架构师修炼之微服务部署 - 容器与集群编排

背景

通过前几章节,我们知道:

  • docker build 可以创建一个自定义镜像;
  • docker run 可以启动一个容器;

而实际项目中,特别是微服务化之后,运维需要面对的不单单是一个镜像一个容器,而是几十乃至上千。如果通过手工敲命令去创建一个个容器,不科学也太慢。如果遇到机器更新换代或者重启,又得重新敲一遍,这样下去我想迟早脑袋头发都要掉光。

还好docker提前想到了这点,为我们准备了相应的工具:

还有一个很时尚的工具 Kubernetes,它是Google根据自身十几年经验打造的。

容器编排:docker compose

Compose 是由Python 编写,定义和运行多个 Docker 容器的工具。通过一个 docker-compose.yml 模板文件(YAML 格式)来定义应用服务,简单的命令批量创建和启动定义的所有服务。

重要概念:

  • 服务 (service):一个应用容器,实际上可以运行多个相同镜像的实例。
  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元。

一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。

特性:

  1. 使用不同的项目名称可以在一个主机构建不同组应用环境。比如开发主机上的不同项目。

    默认项目名称为yml文件所在目录名称,通过使用-p projectname 可以自定义项目名称。创建的镜像和容器名称前缀会带上项目名称。

  2. 保存已经创建容器里头挂载的数据。运行 docker-compose up 的时候,如果监测到有运行的容器,它会自动把就容器挂载的数据复制到新的容器。

  3. Compose会缓存创建容器的配置,重启service时只会重建有更改的容器。

  4. yml模板文件支持可变参数,使用参数可以创建不同的应用。

db:
  image: "postgres:${POSTGRES_VERSION}"

YML模板文件常用配置:

  • version:compose文件格式版本号,最新版为3。

    Compose file formatDocker Engine release
    3.819.03.0+
    3.718.06.0+
    3.618.02.0+
    3.517.12.0+
    3.417.09.0+
    3.317.06.0+
    3.217.04.0+
    3.11.13.1+
    3.01.13.0+
    2.417.12.0+
    2.317.06.0+
    2.21.13.0+
    2.11.12.0+
    2.01.10.0+
    1.01.9.1.+
  • services:服务父节点。

  • 通过镜像创建容器的docker-compose.yml文件

    version: "3"
    services:
      #容器唯一标识
      webapp:
        #镜像名称
        image: examples/web
        ports:
          - "80:80"
        volumes:
          - "/data"
  • 通过DockerFile编译创建容器的docker-compose.yml文件

    version: ‘3‘
    services:
      #容器唯一标识
      webapp:
        build:
          #DockerFile文件地址
          context: ./dir
          #DockerFile文件名称
          dockerfile: Dockerfile-alternate

安装:

  • Mac/Windows安装:Docker Desktop for Mac/Windows 自带 docker-compose 二进制文件,安装 Docker 之后可以直接使用。
  • Linux安装:
    $ sudo curl -L https://github.com/docker/compose/releases/download/1.25.5/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
    
    $ sudo chmod +x /usr/local/bin/docker-compose

常见命令:

  • up: 创建并启动配置的所有容器
  • down: 停止并删除容器、镜像、网络、存储盘
  • ps:列出服务(service)所有的容器
    D:\docker\03-compose>docker-compose ps
              Name                      Command            State    Ports
    ---------------------------------------------------------------------
    03-compose_nbaspnetcore_1   dotnet aspnetcoreapp.dll   Exit 0
    03-compose_nbginx_1         /bin/sh -c /bin/bash       Exit 0
  • images:列出服务(service)所有的镜像
    D:\docker\03-compose>docker-compose images
            Container                 Repository           Tag       Image Id       Size
    --------------------------------------------------------------------------------------
    03-compose_nbaspnetcore_1   03-compose_nbaspnetcore   latest   73ae1ad12839   211.9 MB
    03-compose_nbginx_1         03-compose_nbginx         latest   61ed0bb949f6   403.8 MB
  • rm:删除停止的容器
    D:\docker\03-compose>docker-compose ps
              Name                      Command            State                       Ports
    -----------------------------------------------------------------------------------------------------------
    03-compose_nbaspnetcore_1   dotnet aspnetcoreapp.dll   Up       0.0.0.0:6067->443/tcp, 0.0.0.0:6066->80/tcp
    03-compose_nbginx_1         /bin/sh -c /bin/bash       Exit 0
    
    D:\docker\03-compose>docker-compose rm
    Going to remove 03-compose_nbginx_1
    Are you sure? [yN] y
    Removing 03-compose_nbginx_1 ... done                                                                                   
    D:\docker\03-compose>docker-compose ps
              Name                      Command            State                      Ports
    ----------------------------------------------------------------------------------------------------------
    03-compose_nbaspnetcore_1   dotnet aspnetcoreapp.dll   Up      0.0.0.0:6067->443/tcp, 0.0.0.0:6066->80/tcp
  • build:编译或者再编译服务
  • create: 创建服务
  • start:开启服务
  • stop:停止服务
  • restart:重启服务

实例

下面用 Python 来建立一个能够记录页面访问次数的 web 网站。

架构师修炼之微服务部署 - 容器与集群编排

  1. 新建composetest文件夹,在该目录中编写 app.py 文件。

    import time
    
    import redis
    from flask import Flask
    
    app = Flask(__name__)
    cache = redis.Redis(host=‘redis‘, port=6379)
    
    
    def get_hit_count():
        retries = 5
        while True:
            try:
                return cache.incr(‘hits‘)
            except redis.exceptions.ConnectionError as exc:
                if retries == 0:
                    raise exc
                retries -= 1
                time.sleep(0.5)
    
    
    @app.route(‘/‘)
    def hello():
        count = get_hit_count()
        return ‘Hello World! 该页面已被访问 {} 次。\n‘.format(count)
  2. 在文件夹中,添加 Dockerfile 文件

    FROM python:3.7-alpine
    WORKDIR /code
    ENV FLASK_APP app.py
    ENV FLASK_RUN_HOST 0.0.0.0
    RUN apk add --no-cache gcc musl-dev linux-headers
    RUN pip install redis flask
    COPY . .
    CMD ["flask", "run"]
    • 通过Python 3.7 基础镜像创建.
    • 设置工作目录为 /code.
    • 设置FLask命令的环境变量.
    • 安装gcc 加速编译MarkupSafe与SQLAlchemy.
    • 安装python依赖的包.
    • 复制当前目录内容到镜像目录.
    • 设置容器默认启动命令flask run.
  3. 在文件夹外部创建 docker-compose.yml 文件

    version: ‘3‘
    services: 
      webtest: 
        build: ./composetest
        ports: 
          - 5000:5000
      redis: 
        image: redis:alpine
    • 创建一个web容器,开放5000端口.
    • 从Docker Hub拉取一个Redis镜像创建容器.
  4. 运行Compose项目。(项目名称为plana)

    D:\docker\03-compose>docker-compose -p plana up
    Creating network "plana_default" with the default driver
    Building webtest
    Step 1/8 : FROM python:3.7-alpine
    ---> 16f919b9ecd5
    Step 2/8 : WORKDIR /code
    ---> Using cache
    ---> 3cd944ab4513
    Step 3/8 : ENV FLASK_APP app.py
    ---> Using cache
    ---> 2aa9917f4c9f
    Step 4/8 : ENV FLASK_RUN_HOST 0.0.0.0
    ---> Using cache
    ---> db1028a91223
    Step 5/8 : RUN apk add --no-cache gcc musl-dev linux-headers
    ---> Using cache
    ---> 13a0ed97a7dd
    Step 6/8 : RUN pip install redis flask
    ---> Using cache
    ---> 1807263196e2
    Step 7/8 : COPY . .
    ---> Using cache
    ---> 553b6c68eace
    Step 8/8 : CMD ["flask", "run"]
    ---> Using cache
    ---> ad92e1924ad5
    Successfully built ad92e1924ad5
    Successfully tagged plana_webtest:latest
    WARNING: Image for service webtest was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
    Creating plana_redis_1   ... done                                                                                       Creating plana_webtest_1 ... done                                                                                       Attaching to plana_webtest_1, plana_redis_1
    redis_1    | 1:C 29 Apr 2020 12:59:36.795 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
    redis_1    | 1:C 29 Apr 2020 12:59:36.795 # Redis version=5.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
    redis_1    | 1:C 29 Apr 2020 12:59:36.795 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
    redis_1    | 1:M 29 Apr 2020 12:59:36.800 * Running mode=standalone, port=6379.
    redis_1    | 1:M 29 Apr 2020 12:59:36.800 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
    redis_1    | 1:M 29 Apr 2020 12:59:36.800 # Server initialized
    redis_1    | 1:M 29 Apr 2020 12:59:36.801 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command ‘echo never > /sys/kernel/mm/transparent_hugepage/enabled‘ as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
    redis_1    | 1:M 29 Apr 2020 12:59:36.802 * Ready to accept connections
    webtest_1  |  * Serving Flask app "app.py"
    webtest_1  |  * Environment: production
    webtest_1  |    WARNING: This is a development server. Do not use it in a production deployment.
    webtest_1  |    Use a production WSGI server instead.
    webtest_1  |  * Debug mode: off
    webtest_1  |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
  5. 执行结果。(需要指定项目名称)

    D:\docker\03-compose>docker-compose ps
    Name   Command   State   Ports
    ------------------------------
    
    D:\docker\03-compose>docker-compose images
    Container   Repository   Tag   Image Id   Size
    ----------------------------------------------
    
    D:\docker\03-compose>docker-compose -p plana images
      Container       Repository      Tag       Image Id       Size
    ------------------------------------------------------------------
    plana_redis_1     redis           alpine   3661c84ee9d0   29.8 MB
    plana_webtest_1   plana_webtest   latest   ad92e1924ad5   219.7 MB
    
    D:\docker\03-compose>docker-compose -p plana ps
        Name                    Command               State           Ports
    ---------------------------------------------------------------------------------
    plana_redis_1     docker-entrypoint.sh redis ...   Up      6379/tcp
    plana_webtest_1   flask run                        Up      0.0.0.0:5000->5000/tcp

    架构师修炼之微服务部署 - 容器与集群编排

集群编排:swarm mode

Swarm 是使用 SwarmKit 构建的 Docker 引擎内置(原生)的集群管理和编排工具。
Docker 1.12开始 Swarm mode 已经内嵌入 Docker 引擎,成为了 docker 子命令 docker swarm

Swarm mode 内置 kv 存储功能,提供了众多的新特性,使得 Docker 原生的 Swarm 集群具备与 Mesos、Kubernetes 竞争的实力。比如:

  • 具有容错能力的去中心化设计
  • 内置服务发现
  • 负载均衡
  • 路由网格
  • 动态伸缩
  • 滚动更新
  • 安全传输等

常见概念

  1. 节点

    节点为作用在Swarm集群中的一个实例化Docker引擎。虽然可以在一台物理主机上部署多个节点,但是生产环境中都是跨物理主机进行。
    节点可以加入已经存在的swarm集群或者将自己初始化为一个swarm集群。

    分为:管理(manager)节点,工作(worker)节点。

    架构师修炼之微服务部署 - 容器与集群编排

    • 管理节点:

      执行docker swarm 命令管理 swarm 集群的节点。

      一个 Swarm 集群可以有多个管理节点,但只有一个管理节点可以成为 leader,leader 通过 raft 协议实现。

      管理节点下发任务(Task) 给工作节点,通过工作节点通知给它的任务(Task)执行状态来管理每个工作节点。
    • 工作节点:

      接收和执行来自管理节点的任务(task)。管理节点默认也作为工作节点。你也可以通过配置让服务只运行在管理节点。
  2. 服务(Service)与任务(Task)

    架构师修炼之微服务部署 - 容器与集群编排

    • 任务(Task):

      Swarm 中的最小的调度单位,目前来说就是一个单一的容器。一个任务分配给一个节点之后,哪怕执行失败都无法转移到其他节点。
    • 服务(Service):

      运行于管理或者工作节点上的一组任务属性定义。它是Swarm的中心结构,也是与Swarm交互的主要角色。

      与Docker-compose中的服务类似,包含镜像与命令等。

      服务有两种模式:
      • replicated services:按照一定规则在各个工作节点上运行指定个数的任务。
      • global services:每个工作节点上运行一个任务

使用实例

使用play-with-docker实验。

  1. 初始化管理节点

    $ docker swarm init
    Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on different interfaces (192.168.0.48 on eth0 and 172.18.0.53 on eth1) - specify one with --advertise-addr

    因为有多个网络地址需要指定

    $ docker swarm init --advertise-addr 192.168.0.48
    Swarm initialized: current node (uas5ayulwp28ldrcr3bscmitr) is now a manager.
    
    To add a worker to this swarm, run the following command:
    
        docker swarm join --token SWMTKN-1-17khjeqkfmjswynhda1i0dfqw4hy5io2006xktyvav272ccfie-6xomx3u1mvm7pacj0ymheyu6e 192.168.0.48:2377
    
    To add a manager to this swarm, run ‘docker swarm join-token manager‘ and follow the instructions.

    查看节点列表

    $ docker node ls
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
    uas5ayulwp28ldrcr3bscmitr *   node1               Ready               Active              Leader              19.03.4
  2. 加入工作节点

    $ docker swarm join --token SWMTKN-1-17khjeqkfmjswynhda1i0dfqw4hy5io2006xktyvav272ccfie-6xomx3u1mvm7pacj0ymheyu6e 192.168.0.48:2377
    This node joined a swarm as a worker.

    在两个主机执行以上命令之后,去管理节点查看节点列表

    $ docker node ls
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
    uas5ayulwp28ldrcr3bscmitr *   node1               Ready               Active              Leader              19.03.4
    2rum5pm81t76tjv91rld1n8vu     node3               Ready               Active                                  19.03.4
    u4tbyt95nhy7l6vj3z013r8wz     node5               Ready               Active                                  19.03.4
  3. 服务部署

    (命令都是在管理节点执行。)

    • 新建服务

      在集群中创建nginx服务。

      $ docker service create --replicas 3 -p 80:80 --name nginx nginx:latest
      stieixvmvfipi4w6qh49t9es0
      overall progress: 3 out of 3 tasks 
      1/3: running   
      2/3: running   
      3/3: running   
      verify: Service converged
    • 查看服务

      查看服务列表:

      $ docker service ls
      ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
      stieixvmvfip        nginx               replicated          3/3                 nginx:latest        *:80->80/tcp

      查看服务详细:

      $ docker service ps nginx
      ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
      xgc5u5m5gl9p        nginx.1             nginx:latest        node3               Running             Running 2 minutes ago                       
      y76pttbz9pkh        nginx.2             nginx:latest        node5               Running             Running 2 minutes ago                       
      oti754af3izt        nginx.3             nginx:latest        node1               Running             Running 2 minutes ago

      浏览器中输入管理节点或者工作节点IP地址都可以查看nginx界面。

      管理节点:

      架构师修炼之微服务部署 - 容器与集群编排

      工作节点1:

      架构师修炼之微服务部署 - 容器与集群编排

      工作节点2:

      架构师修炼之微服务部署 - 容器与集群编排

    • 服务伸缩

      • 业务访问量少时,可以减少服务运行容器数。
        $ docker service scale nginx=2
        nginx scaled to 2
        overall progress: 2 out of 2 tasks 
        1/2: running   
        2/2: running   
        verify: Service converged
        查看服务详情:
        $ docker service ls
        ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
        stieixvmvfip        nginx               replicated          2/2                 nginx:latest        *:80->80/tcp
      • 业务访问量多时,可以增加服务运行容器数。
        $ docker service scale nginx=3
        nginx scaled to 3
        overall progress: 3 out of 3 tasks 
        1/3: running   
        2/3: running   
        3/3: running   
        verify: Service converged
    • 删除服务

      $ docker service rm nginx
      nginx

      查看服务:

      $ docker service ls
      ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
  4. 通过compose文件部署服务

    docker service create 一次只能部署一个服务,使用 docker-compose.yml 可以一次启动多个关联的服务

    • 创建docker-compose.yml文件
      version: "3"
      services:
        nginx:
          image: nginx:latest
          ports:
            - 80:80
          deploy:
            mode: replicated
            replicas: 3
    • 部署服务

      yml文件通过使用docker stack部署。
      $ docker stack deploy -c docker-compose.yml  nginx
      Creating network nginx_default
      Creating service nginx_nginx
    • 查看服务
      $ docker stack ls
      NAME                SERVICES            ORCHESTRATOR
      nginx               1                   Swarm
      $ docker service ls
      ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
      92n4xd254vu1        nginx_nginx         replicated          3/3                 nginx:latest        *:80->80/tcp
      $ docker service ps nginx_nginx
      ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
      p6d3xibx9swx        nginx_nginx.1       nginx:latest        node1               Running             Running 2 minutes ago                       
      v0taw0rnr9v3        nginx_nginx.2       nginx:latest        node3               Running             Running 2 minutes ago                       
      2z3sbkdo16oi        nginx_nginx.3       nginx:latest        node5               Running             Running 2 minutes ago
    • 移除服务
      $ docker stack down nginx
      Removing service nginx_nginx
      Removing network nginx_default