第5章

容 器 编 排




5.1容器编排简介
首先理解编排两个字的含义,编排在计算机领域是指对系统的自动配置和管理。
像Ansible这样的工具就是用来对服务器进行配置和管理的编排工具,而容器编排是指容器的配置、部署和管理等任务的自动化。容器编排可以把开发者从复杂且重复的容器管理工作中拯救出来。
如果只需管理一个容器,则大可不必使用容器编排,但现实是一个容器往往无法承载日益复杂的应用,如果把应用所有的组件都放到一个容器中运行,就丧失了使用容器的诸多优势,无法完成快速扩容缩容、无法对某个组件进行单独部署和升级及无法灵活地调度到其他服务器。现在微服务的概念已经深入人心,越来越多的应用以松耦合的形式来设计架构。对这样的应用容器化后,一定是以多容器的形式来运行的。
从4.6节手动运行和管理容器的过程中可以体会到多容器的管理比较复杂。手工管理不
仅非常低效,而且极易出错,在容器数量增长到一定量级的情况下,使用容器编排的必要性就更加显著。尤其是对于稳定性有极高要求的生产环境,最大可能减少手工操作就能降低出错的风险。
中大型的应用一般会由多个组件构成,例如一个常见Web应用程序可能需要同时用到
Web服务器容器、数据库容器及用作缓存的Redis容器,后续可能会引入消息队列容器。把应用容器化之后,每个组件都会运行在独立的容器中。这样应用就需要多个容器紧密配合才能运行。为了让多个容器正确协作,需要保证容器按一定的顺序启动,需要维持运行中的容器数量,需要控制各个容器的状态,通过容器编排技术,可以自动化完成这些任务,使多个容器组成的整体达到期望的状态。
容器编排是使用容器的实践中比较复杂的一个课题。Docker提供了一定的容器编排的
能力,而作为容器编排领域事实标准的Kubernetes提供了更全面、更强大的编排能力。
5.2Docker Compose
Docker提供了Compose组件,用于实现简单的容器编排。Docker Compose会从文件中读取应用所需的全部容器的定义,这个文件默认的文件名是dockercompose.yaml,可以把它称为Compose文件。有了这个文件以后,只需运行dockercompose up命令就可以把应用所需的所有容器启动,把所需的网络和存储准备就绪。Compose文件相当于把项目的运行环境文档化。实现了用一个命令就完成了烦琐启动多个容器的任务。提供给项目一个完整且隔离的运行环境。
Docker Compose只能管理单节点上的容器,所以更适合在开发和测试环境下使用。
下面使用Docker Compose对第1章中介绍的应用作容器编排。
5.2.1Compose文件
在后端项目的根目录下定义一个dockercompose.yaml文件,内容如下: 



version: "3.9"



services:

web:

image: accounts-frontend:v1.0.0

depends_on:

- api

ports:

- "8088:80"



api:

depends_on:

- mysql

image: accounts:v1.0.0

ports:

- "8081:80"

volumes:

- ./config:/config

restart: always



mysql:

image: mysql:5.7

volumes:

- mysql:/var/lib/mysql

restart: always

environment:

MYSQL_ROOT_PASSWORD: 123

MYSQL_DATABASE: accounts

command: --character-set-server=utf8mb4 --default-time-zone=+08:00



db-migration:

image: goose

depends_on:




 


- mysql

working_dir: /migrations

volumes:

- ./src/database/migrations:/migrations

command: 'goose up'

environment:

GOOSE_DRIVER: mysql

GOOSE_DBSTRING: root:123@tcp(mysql)/accounts



volumes:

mysql:

external: true





下面分析一下这个文件的内容。
下面一行定义了Compose文件格式的版本,推荐使用最新的版本,但是也要注意和Docker的版本兼容,代码如下: 



version: "3.9"





从这一行开始定义应用需要的所有服务。服务是对多个运行相同任务的容器的抽象。因为Compose支持扩展运行某个服务的实例数,所以服务会运行在一个或多个容器实例中,代码如下: 



services: 





定义前端项目accountsfrontend提供的服务,并把它命名为web,web这个名字将成为这个服务中容器的网络别名,也就是说,同一网络中的其他容器可以使用web作为主机名解析到这个容器。image字段用于指定web容器的镜像。depends_on字段用于指定web容器依赖的其他所有容器。depends_on字段决定了容器启动的顺序,它会使依赖的容器先行启动,然后启动web容器。ports字段定义了容器端口的映射,会把宿主机的TCP 8080端口映射到容器中的TCP 80端口,代码如下: 



web:

image: accounts-frontend:v1.0.0

depends_on:

- api

ports:

- "8088:80"





定义后端项目accounts提供的服务,并把它命名为api。depends_on字段用于指定这个服务依赖于mysql服务。ports字段把宿主机的TCP 8081端口映射到容器中的TCP 80端口。volumes字段用于将宿主机上与这个dockercompose.yaml文件同级的config文件挂载到容器中的/config文件夹下。restart字段用于指定容器在中止运行后重启的策略,always表示在任何情况下中止运行后都会重启,代码如下: 



api:

depends_on:

- mysql

image: accounts:v1.0.0

ports:

- "8081:80"

volumes:

- ./config:/config

restart: always





定义MySQL服务并把它命名为mysql。image字段指定了容器的镜像是mysql:5.7。volumes字段用于把名为mysql的数据卷挂载到容器中的/var/lib/mysql文件夹下。
environment字段用于定义容器中的环境变量。这里定义了MYSQL_ROOT_PASSWORD,用来设置root用户的密码。MYSQL_DATABASE环境变量用来设置MySQL启动时自动创建的数据库名字,当指定的数据库已经存在时,不会影响现有的数据。
command中的charactersetserver=utf8mb4指定了MySQL启动时的默认字符集为utf8mb4,不设置默认字符集会默认为latin1,defaulttimezone=+08:00将默认时区设置为东八时区。由于mysql容器的默认执行的命令是mysqld,代码如下: 



mysql:

image: mysql:5.7

volumes:

- mysql:/var/lib/mysql

restart: always

environment:

MYSQL_ROOT_PASSWORD: 123

MYSQL_DATABASE: accounts

command: --character-set-server=utf8mb4 --default-time-zone=+08:00





定义运行数据迁移的服务并把它命名为dbmigration。image字段指定了容器的镜像是goose,代码如下: 



db-migration:

image: goose

depends_on:

- mysql

working_dir: /migrations

volumes:

- ./src/database/migrations:/migrations




 


command: 'goose up'

environment:

GOOSE_DRIVER: mysql

GOOSE_DBSTRING: root:123@tcp(mysql)/accounts





定义应用中需要用到的数据卷。第1个数据卷mysql正是上面定义的mysql容器,此容器用来挂载数据卷,规定了它的属性external是true,表示这个数据卷是在外部的Docker中已有的,无须由Compose创建。



volumes:

mysql:

external: true





5.2.2Compose环境变量
目前的Compose文件中没有使用环境变量,当需要改变文件中的某些值的时候需要修改Compose文件,这样不太方便。如果镜像版本发生了变化,就需要在文件中改动。
Compose支持使用环境变量替代Compose文件中的值。例如可以把文件中的这部分代码进行替换: 



image: accounts-frontend: v1.0.0





替换成使用环境变量的格式: 



image: ${ACCOUNTS_FRONTEND_IMAGE}





这样当accountsfrontend镜像发生变化的时候,通过改动环境变量ACCOUNTS_FRONTEND_IMAGE就可以避免修改Compose文件。例如当镜像版本升级到v1.1.0时,把ACCOUNTS_FRONTEND_IMAGE环境变量设置为accountsfrontend: v1.1.0,这样Compose就会在启动后自动把Compose文件中相应的变量替换成accountsfrontend: v1.1.0。如果Compose文件中用到的环境变量没有定义,则最终对应的位置会变成一个空字符串。
也可以把环境变量写到一个叫作.env的文件中,这会使环境变量的管理变得方便。Compose在启动时会自动读取Compose文件所在文件夹下的.env文件。在.env文件中写入以下内容: 



ACCOUNTS_FRONTEND_IMAGE=accounts-frontend: v1.0.0





为了验证环境变量配置是否正确,预览一下经过替换变量处理后的Compose文件,命令如下: 



docker-compose config





在输出的信息中会看到变量ACCOUNTS_FRONTEND_IMAGE替换后的结果如下: 



image: accounts-frontend: v1.0.0





如果环境变量文件的位置和Compose文件不在同一个文件夹下或者文件名并不是默认的.env,就可以使用envfile参数来告诉Compose它的具体位置。利用这个功能,可以把不同环境下用到的环境变量写到不同的文件中,例如把集成环境用到的环境变量写到.env.ci文件中,把开发环境用到的环境变量写到.env.dev中。之后在运行Compose的时候把对应的文件通过envfile参数传递到Compose。
如果Compose文件中的某些值重复出现在多个位置,则使用环境变量还可以使修改值的过程变得更简单。不再需要在文件中多处修改同一个值,只需修改一次环境变量。
下面是Compose文件引入更多环境变量后最终的版本: 



version: "3.9"



services:

web:

image: ${ACCOUNT_FRONTEND_IMAGE}

depends_on:

- api

ports:

- "8088:80"



api:

depends_on:

- mysql

image: ${ACCOUNT_IMAGE}

ports:

- "8081:80"

volumes:

- ./config:/config

restart: always

command: go run src/server/main.go



mysql:

image: mysql:5.7

volumes:

- mysql:/var/lib/mysql

restart: always

environment:

MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}




 


MYSQL_DATABASE: ${MYSQL_DB}

command: --character-set-server=utf8mb4 --default-time-zone=+08:00



db-migration:

image: goose

depends_on:

- mysql

working_dir: /migrations

volumes:

- ./src/database/migrations:/migrations

command: goose up

environment:

GOOSE_DRIVER: mysql

GOOSE_DBSTRING: root:${MYSQL_PASSWORD}@tcp(mysql:3306)/${MYSQL_DB}

volumes:

mysql:

external: true





可以看到引入环境变量之后,降低了Compose文件中值的耦合。相应地,需要在.env文件中增加新引入的环境变量,修改后的代码如下: 



ACCOUNTS_FRONTEND_IMAGE=accounts-frontend:v1.0.0

ACCOUNT_IMAGE=accounts:v1.0.0

MYSQL_PASSWORD=123

MYSQL_DB=accounts





5.2.3Compose运行应用
现在dockercompose.yaml文件已经准备好了,下面用Docker Compose来运行应用。命令如下: 



docker-compose up





运行命令后会看到输出信息中包含以下部分: 



Creating network "accounts_default" with the default driver

Creating accounts_mysql_1 ... done

Creating accounts_api_1 ... done

Creating accounts_db-migration_1 ... done

Creating accounts_web_1... done





输出日志比较多,并且都显示在终端,如果希望在后台运行,则可以在命令中加上d参数。如果以d参数运行,则查看日志需要使用dockercompose logs命令。
输出信息第一行表示Docker Compose自动创建了一个基于默认驱动器(也就是bridge)的网络,网络名为accounts_default。这样就为应用提供了网络隔离。
用docker network命令进行验证,命令如下: 



docker network ls

NETWORK ID NAME DRIVERSCOPE

f64a9cafa09e accounts_default bridgelocal





发现多了一个accounts_default网络。
通过Docker Compose运行起来的容器同样也可以用docker ps命令看到。为了方便观察,这里对docker ps命令输出的信息做了适当截取,命令如下: 



docker ps

CONTAINER ID IMAGENAMES

77a16cd7ac42 accounts-frontend:v1.0.0 accounts_web_1

7a5e6fc79984 accounts:v1.0.0 accounts_api_1

608fbe95fc17 mysql:5.7 accounts_mysql_1





下面介绍一下Docker Compose对容器命名的方式。以accounts_web_1容器为例,accounts_web_1中的accounts部分是项目名,默认为用项目根目录的文件夹名字命名,如果希望自己指定项目名,则可以在启动时加上参数p,web是在Compose文件中定义的容器名或者叫服务名,最后的数字1表示web容器的个数,也就是它一共运行了多少个实例。
也可以用Compose提供的命令来查看容器的运行情况,命令如下: 



docker-compose ps





输出如下: 



NameCommand StatePorts

----------------------------------------------------------------------

accounts_api_1/accounts Up0.0.0.0:8081->80/tcp

accounts_db-migration_1goose upExit 1

accounts_mysql_1docker-entrypoint.sh mysqld Up3306/tcp, 33060/tcp

accounts_web_1 /docker-entrypoint.sh ngin ...Up0.0.0.0:8088->80/tcp





和docker ps命令不同的是: 这条命令会只显示dockercompose.yaml文件中有关的容器。这样就可以方便观察。
在输出的信息中可以发现accounts_dbmigration_1的状态是Exit 1,证明容器中产生了某种错误而退出了。
查看Compose输出的日志,会发现下面的信息: 



db-migration_1| 2021/03/05 12:47:25 goose run: dial tcp 172.18.0.2:3306: connect: connection refused





表明dbmigration_1连接MySQL数据库失败。这个容器用来运行goose进行数据迁移,如果无法与数据库时连接,就无法正常运行。
尝试单独运行dbmigration,使用的命令如下: 



docker-compose run db-migration





运行命令会看到以下输出: 



Creating accounts_db-migration_run ... done

2021/03/05 12:56:50 OK20210304203736_create_table_user.sql

2021/03/05 12:56:50 goose: no migrations to run. current version: 20210304203736





这次dbmigration运行成功了,说明之前出现连接失败是因为MySQL没有准备就绪。
虽然这个Compose文件中定义了dbmigration与mysql之间的依赖关系,但是这只能使Compose按照依赖关系的顺序运行容器,并不保证容器就绪的顺序也严格遵从这个依赖关系。只要被依赖的容器开始运行,Compose就会开始创建依赖它的容器。容器进入运行状态不一定表示它已经准备就绪,有的容器如mysql从开始运行到就绪需要一段时间来初始化,mysql就绪的标志是在日志中输出[Note] mysqld: ready for connections。从日志中可以看出mysql容器从启动到就绪所用的时间远大于dbmigration,当dbmigration尝试连接mysql的时候,mysql并没有准备就绪。这就是为什么dbmigration在启动后连接mysql会失败的原因。
同样地,api服务的容器也会由于这个原因在启动过程中连接数据库失败,但是api会在连接数据库失败后不断重试,在mysql就绪以后,重试连接就成功了,所以不需要人工介入accounts也能在最后进入正常状态。
服务就绪顺序与依赖顺序不一致问题的一个很好的解决方案就是提高对依赖的服务处于未就绪状态的容忍度,也就是增加服务的弹性。
到这里应用就已经通过Compose这种方式正常运行起来了。打开网址http://localhost: 8088可以看到前端的页面可正常显示。
当前版本的dockercompose.yaml文件引用了一个外部的mysql数据卷,增加了在其他机器上复现环境的复杂性,在其他机器上要用这个文件运行dockercompose up,也要先手动创建数据卷。在不需要引用外部数据卷的场景下,可以把文件中的volumes部分做如下修改: 



volumes: 

mysql: 





去掉了属性external:true。这样,mysql数据卷就会由Compose来创建和管理了。
为了观察修改后的文件所带来的变化,用Compose重新运行应用。重启之前需要关闭整个应用。命令如下: 



docker-compose down





这个命令会把与应用相关的所有容器都中止,和手动管理每个容器相比,Docker Compose把多个容器作为整体进行管理,带来了很大的便捷。
接着重新运行dockercompose up,会看到输出信息中相较之前多了一行这样的文本: 



Creating volume "accounts_mysql" with default driver





表明Compose创建了一个名为accounts_mysql的数据库,可以用docker volume命令验证,命令如下: 



$ docker volume ls

DRIVERVOLUME NAME

local accounts_mysql





本地的数据卷中出现了新创建的accounts_mysql数据卷。证明Compose会根据文件中的定义自动创建所需要的数据卷。
Compose会保留应用使用的数据卷,下次启动应用时会把上次运行产生的数据卷复制到新的容器中,这样就不会使数据丢失了。
要关闭应用,需要用到下面的命令: 



docker-compose stop





运行命令会看到以下输出信息: 



Stopping accounts_web_1... done

Stopping accounts_api_1 ... done

Stopping accounts_mysql_1... done





可以看到dockercompose stop命令会把与应用相关的所有运行中的容器都中止。
再次启动容器时可以用的命令如下: 



docker-compose start





如果需要中止应用并且把应用产生的所有容器和网络都清除,则可以运行的命令如下: 



docker-compose down





运行命令后输出的信息如下: 



Stopping accounts_web_1... done

Stopping accounts_api_1 ... done




 


Stopping accounts_mysql_1... done

Removing accounts_web_1... done

Removing accounts_api_1 ... done

Removing accounts_db-migration_1 ... done

Removing accounts_mysql_1... done

Removing network accounts_default





在Compose文件中声明了命名数据卷的情况下,为数据安全起见,使用dockercompose down命令关闭应用并不会清除它创建的数据卷。需要加上volume参数才会把数据卷也清除。重新执行dockercompose up启动应用,然后输入的命令如下: 



docker-compose down --volume





运行这条命令会看到下面的输出信息: 



Stopping accounts_web_1... done

Stopping accounts_api_1 ... done

Stopping accounts_mysql_1... done

Removing accounts_web_1... done

Removing accounts_db-migration_1 ... done

Removing accounts_api_1 ... done

Removing accounts_mysql_1... done

Removing network accounts_default

Removing volume accounts_mysql





可以看到加了volume参数后,输出信息中出现了下面这一行: 



Removing volume accounts_mysql





说明把数据卷accounts_mysql删除了。
5.2.4Compose更新应用
当更新应用代码以后,会构建出新版本的容器镜像,接下来应如何对运行中的应用进行升级呢?不需要手动中止旧容器,然后运行新容器来完成升级,只需修改Compose文件中相应的镜像版本,修改完Compose文件后,重新运行dockercompose up,Compose就会自动中止旧容器,创建新容器。这样就完成了对应用的更新。
5.3Docker Swarm
与Docker Compose只能管理本地单个节点上的容器不同的是Docker Swarm可以管理多个节点上的容器,也就是具有了管理Docker集群的能力。学习Swarm可以熟悉运维和管理容器集群过程中会遇到的一些常见问题。
使用Swarm要求Docker Engine的版本不低于1.12。
Swarm集群中的节点分为manager节点和worker节点。节点即Docker主机,也就是运行Docker Engine的机器,Swarm支持在多个节点上运行,也支持在单节点上运行。manager节点也可以执行worker节点的任务,当然也可以设置成manager节点只执行manager的任务。Swarm中的节点必须以Swarm模式运行。
在分布式环境中,节点之间需要确保安全的通信,不仅需要对通信进行加密,还需对身份进行验证。Swarm集群节点之间通过双向TLS实现安全通信,当运行docker swarm init命令时,这个manager节点上会生成一个新的root CA和一对公私钥。同时manager节点还会生成2个token,一个是manager token用于其他manager节点加入集群,另一个是worker token,用于其他worker节点加入集群。每个token都包含root CA证书的摘要信息还有一个随机生成的密钥。当节点加入集群时,节点就可以根据token中的root CA摘要来验证manager的真实性,而manager节点用token中的密钥来验证待加入节点的有效性。
当节点加入集群时,还会在节点生成一对公私钥及root CA证书。文件保存在/var/lib/docker/swarm/certificates下。其中公钥证书的文件名是swarmnode.crt,私钥的文件名为swarmnode.key。root CA的证书名为swarmrootca.crt。swarmrootca.crt文件内容和集群中第1个manager节点上的root CA的文件内容完全一致。
manager节点负责管理worker节点,具有leader身份的manager节点负责将任务分配到worker节点。manager节点之间使用Raft算法来达成共识。所有的manager节点都参与维护Swarm集群内部状态,使其在不同的manager节点保持一致。
Swarm集群中的manager节点数量决定了集群的容错能力,如果集群节点数是n,则集群最多可以容忍(n-1/2)个节点失败。例如3节点的集群,最多可以容忍1个节点失败,4节点的集群,同样也只能容忍1个节点失败,所以最好使用奇数个manager节点。偶数个节点和比它小1的奇数相比并不能提高集群的容错能力。为了更佳的可用性,可以把manager节点分布到不同的IDC区域。
虽然增加manager数量可以提高集群容错能力,但是也会影响Swarm的性能。Docker官方建议使用的manager节点数不超过7个。
worker节点只负责运行服务而不负责管理集群。所有的worker节点上都运行一个代理,用来通知master节点自己的状态。这样master节点就可以根据通知来对worker节点进行管理。在worker上的任务失败后,manager可以把这个任务调度到其他节点执行。
5.3.1创建Swarm集群
下面演示Swarm集群功能,准备3台Linux服务器,1台作为manager节点,其余2台作为worker节点。所有服务器都安装了Docker。3台服务器的操作系统都是Ubuntu 20.04。
在第1台服务器上创建Swarm集群的manager节点,命令如下: 



docker swarm init --advertise-addr 54.255.243.130





命令中的advertise参数用来指定manager节点的IP地址。运行命令输出的信息如下: 



Swarm initialized: current node (f2hkxhks4g4msvann1ehargyy) is now a manager.



To add a worker to this swarm, run the following command:



docker swarm join --token \

SWMTKN-1-4loyfwaqwwoqdoutbscaw0t877h4o3pnoa0yytf6t5mstd5uwj-e97svc3o150sm7xp8qx18ad5p 54.255.243.130:2377



To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.





表示集群中的manager节点启动了。输出信息中提示worker节点加入这个集群的命令如下: 



docker swarm join --token \

SWMTKN-1-4loyfwaqwwoqdoutbscaw0t877h4o3pnoa0yytf6t5mstd5uwj-e97svc3o150sm7xp8qx18ad5p 54.255.243.130:2377





之后会在worker节点上运行此命令来加入集群。
worker加入集群用到的worker token后续还可以单独查询,命令如下: 



docker swarm join-token worker





运行命令后输出的信息如下: 



To add a worker to this swarm, run the following command:



docker swarm join --token \

SWMTKN-1-4loyfwaqwwoqdoutbscaw0t877h4o3pnoa0yytf6t5mstd5uwj-e97svc3o150sm7xp8qx18ad5p 54.255.243.130:2377





如果希望其他节点以manager角色加入集群,则需要获取manager token,命令如下: 



docker swarm join-token manager





运行命令后输出的信息如下: 



To add a manager to this swarm, run the following command:



docker swarm join --token \

SWMTKN-1-55zvv6p3lqogypvzwmbo76sd9c90wx2l4a74o4myqgs3cq4ht8-btap3xq3znef742sageey94yt 172.31.30.250:2377





创建集群后,查看一下目前集群中的所有节点,命令如下: 



docker node ls





运行命令后输出类似如下信息: 



ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION

r29f.. * ip-172-31-30-250 ReadyActive Leader20.10.5





在输出信息中对ID列做了截取,STATUS列是Active,表示manager节点运行正常。
下面在第2台服务器(作为worker节点)运行命令如下: 



docker swarm join --token \

SWMTKN-1-4loyfwaqwwoqdoutbscaw0t877h4o3pnoa0yytf6t5mstd5uwj-e97svc3o150sm7xp8qx18ad5p 54.255.243.130:2377





运行命令后输出的信息如下: 



This node joined a swarm as a worker.





表示这台服务器作为worker节点成功加入了Swarm集群中。
同样地,在第3台服务器也执行同样的操作,命令如下: 



docker swarm join --token \

SWMTKN-1-4loyfwaqwwoqdoutbscaw0t877h4o3pnoa0yytf6t5mstd5uwj-e97svc3o150sm7xp8qx18ad5p 54.255.243.130:2377





同样地,会输出如下信息: 



This node joined a swarm as a worker.





现在重新查看集群中的节点,命令如下: 



docker node ls





运行命令后输出类似如下信息(前4列): 



ID HOSTNAME STATUSAVAILABILITY

f2hkxhks4g4msvann1ehargyy * ip-172-31-30-250Ready Active

iobh29mwpmnq0fu3w1q85qt73 ip-172-31-32-49  Ready Active 

t1e9y94rrw6rrrxz16og1djt5 ip-172-31-35-212Ready Active 





输出信息中后2列的内容如下: 



MANAGER STATUSENGINE VERSION

Leader20.10.5

20.10.5

20.10.5






表示3台服务器节点都成功加入了一个Swarm集群。输出信息中的STATUS列全部是Ready,表示集群中的节点都处于正常状态。AVAILABILITY列全部是Active,表示节点都可以用于运行服务所包含的任务,也就是说可以把运行任务的容器调度到这些节点上。
管理Swarm集群时需要在manager节点运行命令,如果在worker节点运行管理Swarm集群相关的命令,则提示如下: 



Error response from daemon: This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.





目前集群中有了3个节点,1个manage,2个worker,为了提升集群的容错能力,需要至少3个manager节点,可以在节点加入集群时使用manager token来成为manager节点,也可以把现在集群中的2个worker节点升级为manager节点,命令如下: 



docker node promote ip-172-31-32-49





运行命令后输出的信息如下: 



Node ip-172-31-32-49 promoted to a manager in the swarm.





同样地,将另外一个worker节点也升级为manager节点,命令如下: 



docker node promote ip-172-31-35-212





重新查看集群节点信息,命令如下: 



docker node ls





输出信息如下: 



IDHOSTNAMESTATUS  AVAILABILITY 

r29f4jyze4zlaydw5gqvljb2w * ip-172-31-30-250 Ready Active

uks3x7m5xth4efe6ywn5q4ost ip-172-31-32-49Ready Active

a4odmh76r7983qskgix939os9 ip-172-31-35-212 Ready Active





后两列信息如下: 



MANAGER STATUS ENGINE VERSION

Leader 20.10.5

Reachable20.10.5

Reachable20.10.5





同样支持把manager节点降级为worker节点,命令如下: 



docker node demote ip-172-31-32-49





运行命令后输出的信息如下: 



Manager ip-172-31-32-49 demoted in the swarm.





如果需要把节点从集群中移除,可以在节点上运行命令docker swarm leave。
5.3.2将样例服务部署到Swarm集群
Swarm中运行的任务称为服务。1个服务可以有多个容器实例,可部署到1个或多个节点上。Swarm中服务的部署模式分为两种,一种是replicated,另一种是global。replicated是默认的部署模式,在这种模式下,服务可以运行多个副本,但是不能保证服务在每个节点都有容器实例在运行,而在global模式下,集群中的所有节点都会部署1个服务实例。新加入集群的节点也会由Swarm自动启动1个服务实例。通常需要以global模式部署的服务,包括日志收集、监控系统及其他需要在每个节点都运行的服务。
下面演示如何在Swarm集群部署服务。登录到已经搭建好的集群中的第1台服务器,也就是manager节点,然后创建一个简单的服务,命令如下: 



docker service create --replicas 1 --name helloworld alpine ping docker.com





命令会生成一个alpine镜像的容器,在容器中运行ping命令。replicas参数用来设置同时运行多少个容器实例,replicas 1表示只运行一个容器实例。运行命令后输出的信息如下: 



ew7ri8clfc34fmtrhq00t2o9h

overall progress: 1 out of 1 tasks

1/1: running [==================================================>]

verify: Service converged





如果输出信息,则表示服务创建成功了。
下面查看Docker中的容器信息,命令如下: 



docker ps





运行命令后输出的信息如下: 



CONTAINER ID IMAGECOMMAND CREATED STATUS 

fe9661fe7f93 alpine:latest"ping docker.com"8 seconds ago Up 7 seconds





还可以用docker service命令查看Swarm集群中的服务列表,命令如下: 



docker service ls





运行命令后输出的信息如下: 



ID NAMEMODEREPLICAS IMAGE PORTS

ew7ri8clfc34 helloworld replicated 1/1 alpine:latest





输出信息中的REPLICAS列显示的1/1表示服务一共有1个容器在运行,期望运行的实例数是1。
查看服务的详细信息,命令如下: 



docker service inspect --pretty helloworld





运行命令后输出的信息如下: 



ID:ew7ri8clfc34fmtrhq00t2o9h

Name:helloworld

Service Mode:Replicated

Replicas:1

Placement:

UpdateConfig:

Parallelism:1

On failure:pause

Monitoring Period: 5s

Max failure ratio: 0

Update order:stop-first

RollbackConfig:

Parallelism:1

On failure:pause

Monitoring Period: 5s

Max failure ratio: 0




 


Rollback order:stop-first

ContainerSpec:

Image:

alpine:latest@sha256:a75afd8b57e7f34e4dad8d65e2c7ba2e1975c795ce1ee22fa34f8cf46f96a3be

Args:ping docker.com

Init:false

Resources:

Endpoint Mode: vip





5.3.3伸缩样例服务
Swarm中的服务可以很方便地进行伸缩,首先登录到manager节点,把helloworld服务扩展到2个容器实例,命令如下: 



docker service scale helloworld=2





运行命令后输出的信息如下: 



helloworld scaled to 2

overall progress: 2 out of 2 tasks

1/2: running [==================================================>]

2/2: running [==================================================>]

verify: Service converged





表示helloworld服务扩展成功,接着查看helloworld服务的容器实例的信息,命令如下: 



docker service ps helloworld





运行命令后输出的信息如下(前5列): 



ID NAME IMAGENODE DESIRED STATE

84361iq8n17e helloworld.1 alpine:latest ip-172-31-30-250 Running

7bjpfhxppb1q helloworld.2 alpine:latest ip-172-31-32-49Running





后3列信息如下: 



CURRENT STATEERROR PORTS

Running 2 minutes ago

Running 30 seconds ago





从输出信息中的NODE列可以看出Swarm把helloworld服务的一个容器实例调度到了一个worker节点(ip172313249)。把另一个实例调度到了manager节点(ip1723130250)。
实验结束,删除helloworld服务,命令如下: 



docker service rm helloworld





5.3.4更新样例服务
这一节演示如何对Swarm中的服务升级,首先部署3个使用redis:3.0.6镜像的容器实例,然后升级到redis:3.0.7镜像。Swarm具有支持滚动更新的特性,也就是按照指定的并发度,依次对节点中服务的容器实例进行更新。
下面创建服务redis,命令如下: 



docker service create \

--replicas 3 \

--name redis \

--update-delay 10s \

redis:3.0.6





命令中的replicas 3表示运行3个容器实例,updatedelay 10s表示依次更新每个容器的时间间隔为10s。时间单位可以是小时、分钟和秒,分别用h、m、s表示。可以搭配起来使用,例如1h5m30s表示1小时5分钟30秒。Swarm调度器默认在同一时刻只会更新1个容器实例,如果想改变同一时刻更新容器的数量,则需要使用参数updateparallelism。例如指定updateparallelism 3就会同时对3个容器实例进行更新。
运行命令后输出的信息如下: 



5rf7xzeu2rmw2e8j65mk4spm7

overall progress: 3 out of 3 tasks

1/3: running [==================================================>]

2/3: running [==================================================>]

3/3: running [==================================================>]

verify: Service converged






输出信息表示已经成功运行了3个容器,查看Swarm中的服务列表,命令如下: 



docker service ls





运行命令后输出的信息如下: 



IDNAMEMODEREPLICASIMAGEPORTS

5rf7xzeu2rmwredisreplicated3/3redis: 3.0.6





进一步查看运行redis服务的容器信息和所在节点,命令如下: 



docker service ps redis





运行命令后输出的信息如下(前4列): 



ID NAMEIMAGE NODE DESIRED STATE

z78tjmschmpa redis.1 redis:3.0.6 ip-172-31-35-212 Running

0ftx5ta6mq7t redis.2 redis:3.0.6 ip-172-31-32-49Running

etui5zcr9xt9 redis.3 redis:3.0.6 ip-172-31-30-250 Running





后3列信息如下: 



CURRENTSTATE  ERRORPORTS

Running49secondsago

Running48secondsago

Running48secondsago





从NODE列可以看出3个容器实例运行在3个不同的节点上。
查看redis服务的详细信息,命令如下: 



docker service inspect --pretty redis





pretty参数表示不使用JSON格式输出。运行命令后输出的信息如下: 



ID:5rf7xzeu2rmw2e8j65mk4spm7

Name:redis

Service Mode:Replicated

Replicas:3

Placement:

UpdateConfig:

Parallelism:1

Delay:10s

On failure:pause

Monitoring Period: 5s

Max failure ratio: 0

Update order:stop-first

RollbackConfig:

Parallelism:1

On failure:pause

Monitoring Period: 5s

Max failure ratio: 0

Rollback order:stop-first

ContainerSpec:

Image:

redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842




 


Init:false

Resources:

Endpoint Mode: vip





在UpdateConfig条目下有一项Parallelism:1,表示更新服务的时候最多只能同时更新一个容器实例。也就是更新操作的并行度,可以想象,这个值越大,更新全部服务所用的时间就会越短,但是为了系统的稳定性,通常会选择增量更新。
下面演示把redis服务的镜像升级到3.0.7,同样需要在manager节点上操作。命令如下: 



docker service update --image redis:3.0.7 redis





运行命令后输出的信息如下: 



redis

overall progress: 3 out of 3 tasks

1/3: running [==================================================>]

2/3: running [==================================================>]

3/3: running [==================================================>]

verify: Service converged





重新查看redis服务的详细信息,命令如下: 



docker service inspect --pretty redis





运行命令后输出的信息如下: 



ID:5rf7xzeu2rmw2e8j65mk4spm7

Name:redis

Service Mode:Replicated

Replicas:3

UpdateStatus:

State:updating

Started:55 seconds ago

Message:update in progress

Placement:

UpdateConfig:

Parallelism:1

Delay:10s

On failure:pause

Monitoring Period: 5s

Max failure ratio: 0

Update order:stop-first




 


RollbackConfig:

Parallelism:1

On failure:pause

Monitoring Period: 5s

Max failure ratio: 0

Rollback order:stop-first

ContainerSpec:

Image:

redis:3.0.7@sha256:730b765df9fe96af414da64a2b67f3a5f70b8fd13a31e5096fee4807ed802e20

Init:false

Resources:

Endpoint Mode:vip





5.3.5维护Swarm节点
当Swarm集群中的节点需要维护时,需要把节点上运行的任务调度到正常工作的节点,在把节点设置为维护状态时,Swarm会自动完成任务调度。
下面演示维护节点时Swarm自动调度任务的过程。
首先登录到manager节点,创建一个服务,命令如下: 



docker service create --replicas 3 --name redis --update-delay 10s redis:3.0.6





运行命令,以便创建3个运行redis:3.0.6镜像的容器作为redis服务。
查看运行redis服务的节点信息,命令如下: 



docker service ps redis





运行命令后输出的信息如下(前5列): 



ID NAMEIMAGE NODE DESIRED STATE

hjrzup5y7gxj redis.1 redis:3.0.6 ip-172-31-32-49Running 

xegd5ktg3yo5 redis.2 redis:3.0.6 ip-172-31-30-250 Running 

se1tz60os1u1 redis.3 redis:3.0.6 ip-172-31-35-212 Running 





后3列信息如下: 



CURRENT STATEERROR PORTS

Running 39 seconds ago

Running 39 seconds ago

Running 39 seconds ago





从输出信息中的NODE列可以看出3个容器实例运行在3个不同的节点上。
下面把节点ip172313249设置成维护状态,命令如下: 



docker node update --availability drain ip-172-31-32-49





命令的availability drain表示把节点ip172313249设置为drain,也就是维护状态。drain在英文中是抽干、排干的意思,设置成drain状态的节点上的容器实例会被Swarm结束运行,然后调度到其他正常节点上继续运行。相当于把节点上的容器抽出去,就像把泳池中的水排走一样。
现在再次查看运行redis服务的节点信息,命令如下: 



docker service ps redis





运行命令后输出的信息如下(前5列): 



ID NAMEIMAGE NODEDESIRED STATE 

eo1jkk73u2q9 redis.1 redis:3.0.6 ip-172-31-30-250 Running

hjrzup5y7gxj\_ redis.1 redis:3.0.6 ip-172-31-32-49Shutdown

xegd5ktg3yo5 redis.2 redis:3.0.6 ip-172-31-30-250 Running

se1tz60os1u1 redis.3redis:3.0.6 ip-172-31-35-212 Running





后3列信息如下: 



CURRENT STATEERROR PORTS

Running 1 second ago

Shutdown 2 seconds ago

Running about a minute ago

Running about a minute ago





输出信息中显示节点ip172313249运行redis服务的容器实例被关闭了,Swarm把这个关闭的任务调度到了节点ip1723130250上继续运行,并且保持了原来的实例名字redis.1。这样在节点ip1723130250上就有了2个运行redis服务的容器实例。
可以查看一个节点ip1723130250上的Docker容器的进程,命令如下: 



docker ps





运行命令后输出的信息如下(部分截取): 



CONTAINER ID IMAGE COMMANDCREATED

7e3db2f9b28e redis:3.0.6"/entrypoint.sh redi..."About a minute

agod3e4344a4aaf redis:3.0.6 "/entrypoint.sh redi..."2 minutes ago





输出信息显示有2个redis:3.0.6镜像的容器。
现在节点ip172313249处于不可用于调度的状态,再次查看节点信息,命令如下: 



docker node ls





运行命令后输出的信息如下(前4列): 



IDHOSTNAME STATUSAVAILABILITY

r29f4jyze4zlaydw5gqvljb2w * ip-172-31-30-250 ReadyActive

6zxgzwnpaurtlcd874qozn18l ip-172-31-32-49Ready Drain

a4odmh76r7983qskgix939os9 ip-172-31-35-212 Ready Active





后2列信息如下: 



MANAGER STATUS ENGINE VERSION

Leader 20.10.5

20.10.5

20.10.5





可以看到节点ip172313249对应的AVAILABILITY列显示的状态为Drain。
单独查看节点ip172313249的具体信息,命令如下: 



docker node inspect --pretty ip-172-31-32-49





运行命令后输出的信息如下(部分截取): 



ID:6zxgzwnpaurtlcd874qozn18l

Hostname:ip-172-31-32-49

Joined at: 2021-03-10 02:27:10.221061622 +0000 utc

Status:

State:Ready

Availability: Drain

Address:172.31.32.49






同样可以看到节点ip172313249的Availability属性是Drain。
节点维护结束后,再把它恢复为正常状态。命令如下: 



docker node update --availability active ip-172-31-32-49





恢复为active,也就是活跃状态后,重新查看节点ip172313249的信息,命令如下: 



docker node inspect --pretty ip-172-31-32-49





运行命令后输出的信息如下(部分截取): 



ID:6zxgzwnpaurtlcd874qozn18l

Hostname:ip-172-31-32-49

Joined at: 2021-03-10 02:27:10.221061622 +0000 utc




 


Status:

State:Ready

Availability: Active

Address:172.31.32.49





可以看到节点ip172313249的Availability属性恢复成了Active。这时Swarm又可以调度任务到它上面运行了。
5.3.6Swarm路由网格
Swarm集群中的服务一般会部署到多个节点上运行,当Swarm中的服务通过某个网络端口对外提供服务时,Swarm的Routing Mesh(路由网格)功能使集群中任何一个节点都能在服务暴露的端口上接受请求,包括没有运行服务实例的节点,也可以在服务暴露的端口接受请求。Swarm路由网格会把节点收到的请求自动转发到运行服务的容器实例中。这将有利于提高服务的可用性,例如当某个节点上运行服务的容器实例意外中止后,这个节点上收到的请求会被转发到正常的节点上进行处理。
启用Swarm路由网格功能需要使Swarm集群中的所有节点都开放以下端口: 
TCP或UDP协议的7946端口用于查找网络中的容器,称为容器网络发现,UDP协议的4789端口用于容器入口网络。
在保证节点间可以互相访问上述端口的情况下,创建Swarm集群,这样才能开启Swarm的路由网格功能。这需要设置节点的防火墙或节点使用的网络安全组中的入站策略。
下面演示Swarm路由网格的功能,首先登录到Swarm集群中的manager节点。在manager节点创建一个名为nginx的服务,命令如下: 



docker service create \

--name nginx \

--publish published=8080,target=80 \

--replicas 2 \

nginx





运行命令后输出的信息如下: 



zljsb4htvze5vm7mgj5nb2zfc

overall progress: 2 out of 2 tasks

1/2: running [==================================================>]

2/2: running [==================================================>]

verify: Service converged





现在查看nginx服务的容器实例的节点分布情况,命令如下: 



docker service ps nginx





运行命令后输出的信息如下(前5列): 



ID NAMEIMAGENODE DESIRED STATE

jhp6u085n5rh nginx.1 nginx:latest ip-172-31-30-250 Running

9hdrifxm9xf3 nginx.2 nginx:latest ip-172-31-35-212 Running





后3列信息如下: 



CURRENT STATE ERROR PORTS

Running 4 minutes ago

Running 4 minutes ago





输出信息表示nginx服务现在有两个容器实例,分别运行在节点ip1723130250和节点ip1723135212上。
在节点ip1723130250上验证服务是否正常,命令如下: 



curl localhost: 8080





运行命令后输出的信息如下: 



<!DOCTYPE html>

<html>

<head>

<title>Welcome to Nginx!</title>

...





查看目前集群中的所有节点,命令如下: 



docker node ls





运行命令后输出的信息如下(前4列): 



IDHOSTNAME STATUSAVAILABILITY

r29f4jyze4zlaydw5gqvljb2w * ip-172-31-30-250 ReadyActive

6zxgzwnpaurtlcd874qozn18l ip-172-31-32-49Ready Active

a4odmh76r7983qskgix939os9 ip-172-31-35-212 Ready Active





表明ip172313249节点上没有nginx服务的容器实例,登录到ip172313249节点上查看Docker进程信息来验证一下,命令如下: 



Ubuntu@ip-172-31-32-49:~$ docker ps





运行命令后输出的信息如下: 



CONTAINER IDIMAGECOMMANDCREATEDSTATUSPORTSNAMES






输出信息中没有出现任何运行中的容器,而Swarm路由网格功能可以使集群中没有运行服务实例的节点也可以在服务暴露的端口接受请求。下面验证ip172313249节点是否监听了nginx服务暴露的8080端口,命令如下: 



sudo lsof -i: 8080





这个命令会打印出所有监听8080端口的进程列表,输出的信息如下: 



COMMAND PID USERFDTYPE DEVICE SIZE/OFF NODE NAME

dockerd 577 root20uIPv6691030t0TCP *: httpalt (LISTEN)





可以看到ip172313249节点虽然没有运行nginx的服务容器实例,但是依然监听了nginx服务暴露的8080端口。
尝试访问ip172313249节点上的8080端口,命令如下: 



curl localhost: 8080





运行命令后输出的信息如下: 



<!DOCTYPE html>

<html>

<head>

<title>Welcome to Nginx!</title>

...





证明Swarm的路由网络功能工作正常。
还会对请求做负载均衡,为了验证这一点,在ip172313249节点上多次访问nginx服务,构造一个特殊的地址访问,命令如下: 



curl localhost:8080?node=ip-172-31-32-49





接着登录到ip1723130250,查看nginx服务对应的容器的日志,查看日志命令如下: 



docker logs -f 1f00fef00d2e





其中1f00fef00d2e是运行nginx服务的容器ID。容器ID可通过docker ps命令查询。在输出的日志底部会看到类似的请求日志: 



10.0.0.12 - - [10/Mar/2021:06:37:41 +0000] "GET /?node=ip-172-31-32-49 HTTP/1.1" 200 612 "-""curl/7.68.0""-"





在ip1723135212节点上进行同样的操作,同样会看到类似的请求日志。说明Swarm 路由网络具有负载均衡的功能。
在节点ip1723130250上查看nginx服务日志还可以使用命令docker service logs nginx。这是因为该节点是manager节点。
5.3.7开发环境Swarm部署
1. 准备Swarm环境

与Docker Compose类似,Swarm也支持在描述应用的清单文件中读取容器的所有配置,包括镜像、端口、存储和网络等,并且文件格式和Compose是兼容的。在Swarm中称这个文件为stack文件。
这一节演示把用户认证项目部署到本地开发环境中的Swarm。本地运行单节点的Swarm集群。Docker Desktop默认只支持单节点的Swarm集群。如果需要在本地运行多节点的Swarm,则需要搭配使用Docker Machine。
在使用Swarm之前,需要让Docker Engine以Swarm模式运行。需运行的命令如下: 



docker swarm init





2. 定义stack文件
下面是accounts项目根目录下dockercompose.yaml文件中的内容: 



version: "3.9"



services:

web:

image: ${ACCOUNT_FRONTEND_IMAGE}

depends_on:

- api

ports:

- "8088:80"



api:

depends_on:

- mysql

image: ${ACCOUNT_IMAGE}

ports:

- "8081:80"

volumes:

- ./config:/config

command: go run src/server/main.go

restart: always



MySQL:




 


image: mysql:5.7

volumes:

- mysql:/var/lib/mysql

restart: always

environment:

MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}

MYSQL_DATABASE: ${MYSQL_DB}

command: --character-set-server=utf8mb4 --default-time-zone=+08:00



db-migration:

image: goose

depends_on:

- mysql

working_dir: /migrations

volumes:

- ./src/database/migrations:/migrations

command: goose up

environment:

GOOSE_DRIVER: mysql

GOOSE_DBSTRING: root:${MYSQL_PASSWORD}@tcp(mysql:3306)/${MYSQL_DB}

volumes:

mysql:





把dockercompose.yaml复制为stack.yaml文件,下面会用stack.yaml文件作为Swarm stack文件。
Swarm不支持restart,所以需要修改stack.yaml文件,把restart:always删除掉。
Swarm中的服务在退出后会被自动重启,而dbmigration服务在完成数据库迁移后会自动退出,不需要重启。修改dbmigration服务的重启策略,代码如下: 



db-migration:

deploy:

restart_policy:

condition: none





其他保持不变,添加了deploy字段。用于指定与服务部署相关的配置。deploy字段下的restart_policy用于指定服务的重启策略。restart_policy下的condition用于指定触发重启的条件。默认值是any,也就是任何情况下,只要应用中止运行都会被重启。这里将condition指定为none,意思是任何情况下都不会自动重启。
3. 部署应用
现在就可以用Swarm来部署应用accounts了,在后端项目的根目录下运行的命令如下: 



docker stack deploy --compose-file stack.yaml accounts





可以看到以下输出信息: 



Ignoring unsupported options: restart



Creating network accounts_default

Creating service accounts_mysql

Creating service accounts_web

Creating service accounts_api





这个deploy子命令拥有别名up,也就是说可以把上面命令中的docker stack deploy换成docker stack up。命令中composefile用于指定Compose文件的位置。最后一个参数accounts是为应用所包括的所有容器的集合起的名字。
如何理解docker stack命令名字的含义呢?英文stack的意思是一堆,引申含义是大量、许多。应用包含了多个服务,所以把多服务组成的整体称为stack。
4. 查看服务状态
确认应用包含的容器已经在正常运行,命令如下: 



docker stack services accounts





运行命令后输出的信息如下(前4列): 



ID NAME MODE REPLICAS 

2kxwbk7xo6lk accounts_api  replicated 1/1

fdyzh2ojm1h6 accounts_db-migrationreplicated 0/1

s9lg0nf74b9r accounts_mysql replicated 1/1

237i5hbprlz7 accounts_web replicated 1/1





后2列信息如下: 



IMAGEPORTS

accounts:dev*:8081->80/tcp

goose:latest 

mysql:5.7

accounts-frontend:latest *:8088->80/tcp





输出信息的REPLICAS字段显示api、mysql和web服务都已经正常运行。
5. 关闭应用
关闭整个应用可以使用的命令如下: 



docker stack rm accounts-stack





运行命令后输出的信息如下: 



Removing service accounts-stack_accounts

Removing service accounts-stack_mysql

Removing service accounts-stack_web

Removing network accounts-stack_default





5.3.8生产环境Swarm部署
集群环境下,如果服务可以在任意节点运行将有利于提高服务的可用性。当运行服务的某个节点失败后,可以快速地在其他节点重新启动服务。另外因为这样的服务对节点不挑剔,所以调度成功的概率会更高。
5.3.7节中的dockercompose.yaml文件中的api服务声明需要挂载本地的配置文件./config,代码如下: 



api:

volumes:

- ./config:/config





依赖本地文件就需要在服务启动前在所有节点准备好依赖的文件,这样显然比较烦琐。Docker Swarm支持把配置信息存储在Docker configs中,这样就不需要把配置文件挂载到容器中了。
Docker configs一般用来存储非机密的配置信息,如果需要存储密码、API密钥及证书等机密信息,最好使用Docker secrets。
需要注意的是Docker configs只对Swarm中的服务生效,对于使用docker run命令创建的独立容器和Docker Compose生成的容器则无法使用configs。
当添加新的configs时,configs中的信息会被安全地发送到Swarm中的manager节点,然后存储在Raft日志中。Raft日志会被复制到集群中的所有manager节点中。这样保证了configs数据的高可用性。
服务使用configs后,相应的configs数据会在运行服务的容器中以文件的形式出现。对于Linux环境下的Docker容器,这个文件的默认位置是根目录/<配置名字>下。
1. 定义stack文件
保留用于开发环境的dockercompose.yaml文件,新建stack.yaml文件。把dockercompose.yaml文件的内容复制到stack.yaml文件中,下面开始修改新创建的文件。
在新创建的文件stack.yaml中加入configs定义,代码如下: 



configs:

plain_config:

file: ./config/plain.yaml





接着对api服务相应的部分进行修改,代码如下: 



api:

configs:

- source: plain_config

target: /config/plain.yaml

mode: 0440





configs的定义有两种语法,一种是长语法,另一种是短语法。这里使用的是长语法,长语法提供了更细粒度的参数。source:plain_config表示配置来源是plain_config,也就是引用全局定义的plain_config。target:/config/plain.yaml表示将配置文件挂载到容器中的/config/plain.yaml文件。mode:0440表示将容器中配置文件的读写权限设置为0440。
这样配置文件./config/plain.yaml中的内容就会通过configs的方式出现在api服务的容器中的/config/plain.yaml文件。
配置文件config/plain.yaml中包含了数据库密码,可以把密码等机密信息放到单独的文件,以便分开管理。这样当将应用部署到Swarm中时,可以利用Docker secret来存取数据库密码。
下面把配置文件config/plain.yaml中的数据库密码放到单独的文件config/secret.yaml中,新建文件secret.yaml,在文件中写入的内容如下: 



database: 

password: 123






这时plain.yaml文件的内容如下: 



server:

port: 80

database:

host: mysql

port: 3306

username: root

schema: accounts





在stack.yaml中加入configs定义,代码如下: 



secrets:

secret_config:

file: ./config/secret.yaml





接着在api服务中添加secrets定义,修改后代码如下: 



api:

secrets:




 


- source: secret_config

target: /config/secret.yaml

mode: 0440





修改完成后的stack.yaml内容如下: 



version: "3.9"



services:

web:

image: ${ACCOUNT_FRONTEND_IMAGE}

depends_on:

- api

ports:

- "8088:80"



api:

depends_on:

- mysql

image: ${ACCOUNT_IMAGE}

ports:

- "8081:80"

configs:

- source: plain_config

target: /config/plain.yaml

mode: 0440

secrets:

- source: secret_config

target: /config/secret.yaml

mode: 0440



mysql:

image: mysql:5.7

volumes:

- mysql:/var/lib/mysql

environment:

MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}

MYSQL_DATABASE: ${MYSQL_DB}

command: --character-set-server=utf8mb4 --default-time-zone=+08:00



db-migration:

image: goose

depends_on:

- mysql

working_dir: /migrations




 


volumes:

- ./src/database/migrations:/migrations

command: goose up

environment:

GOOSE_DRIVER: mysql

GOOSE_DBSTRING: root:${MYSQL_PASSWORD}@tcp(mysql)/${MYSQL_DB}

deploy:

restart_policy:

condition: none



volumes:

mysql:



configs:

plain_config:

file: ./config/plain.yaml



secrets:

secret_config:

file: ./config/secret.yaml





接下来登录到manager节点进行操作。
2. 设置环境变量
登录到manager节点以后,把accounts项目复制到当前工作目录下,因为stack.yaml中使用了环境变量,所以需要把.env文件复制到accounts项目根目录下。
.env文件内容如下: 



ACCOUNT_FRONTEND_IMAGE=bitmyth/accounts-frontend:v1.0.0

ACCOUNT_IMAGE=bitmyth/accounts:v1.0.0

MYSQL_PASSWORD=123

MYSQL_DB=accounts





.env文件中出现的镜像都必须从镜像仓库下载。
当使用stack.yaml文件把应用部署到Swarm时,Swarm并不会像Compose一样自动读取.env文件中配置的环境变量,需要手动设置用到的环境变量。命令如下: 



export $(cat .env)





这样就可以使.env文件中定义的全部环境变量在当前的shell进程中生效。
3. 部署应用
完成环境变量设置后,将应用部署到生产环境的Swarm集群中,命令如下: 



docker stack deploy --compose-file stack.yaml accounts





运行命令后输出的内容如下: 



Ignoring unsupported options: restart



Creating network accounts_default

Creating secret accounts_secret_config

Creating config accounts_plain_config

Creating service accounts_api

Creating service accounts_mysql

Creating service accounts_db-migration

Creating service accounts_web





输出信息表示所有的服务都已经开始创建了,并且创建了一个config,名字叫accounts_plain_config。另外创建了一个secret,名字叫accounts_secret_config。
用docker config命令可查看config列表,命令如下: 



docker config ls





运行命令后输出的信息如下: 



ID NAME CREATEDUPDATED

pte1p0uyrb7021wioxhrqnt64accounts_plain_config21 seconds ago21 seconds ago





继续查看accounts_plain_config的详细内容,命令如下: 



docker config inspect accounts_plain_config





运行命令后输出的信息如下: 



[

{

"ID": "pte1p0uyrb7021wioxhrqnt64",

"Version": {

"Index": 39458

},

"CreatedAt": "2021-03-11T01:31:52.595487186Z",

"UpdatedAt": "2021-03-11T01:31:52.595487186Z",

"Spec": {

"Name": "accounts_plain_config",

"Labels": {

"com.docker.stack.namespace": "accounts"

},

"Data": "c2VydmVyOgogIHBvcnQ6IDgwCmRhdGFiYXNlOgogIGhvc3Q6IG15c3FsCiAgcG9ydDogMzMwNgogIHVz
ZXJuYW1lOiByb290CiAgcGFzc3dvcmQ6IDEyMwogIHNjaGVtYTogYWNjb3VudHMK"

}




 


}

]





输出信息中显示的Data字段就是配置的具体内容,这是base64编码后的结果。可以用命令base64来解码查看,命令如下: 



base64 -d <(echo c2VydmVyOgogIHBvcnQ6IDgwCmRhdGFiYXNlOgogIGhvc3Q6IG15c3FsCiAgcG9ydDogMzMw
NgogIHVzZXJuYW1lOiByb290CiAgcGFzc3dvcmQ6IDEyMwogIHNjaGVtYTogYWNjb3VudHMK)





运行命令后输出的解码的内容如下: 



server:

port: 80

database:

host: mysql

port: 3306

username: root

password: 123

schema: accounts





对比这个输出内容和./config/plain.yaml文件,发现它们是一致的。
下面查看secret列表以确认secret是否创建成功,命令如下: 



docker secret ls





运行命令后输出的信息如下: 



IDNAMEDRIVERCREATEDUPDATED

sicei6vjehoh8hj4b64ia2esiaccounts_secret_config2 minutes ago2 minutes ago





继续查看accounts_secret_config的详细内容,命令如下: 



docker inspect accounts_secret_config





运行命令后输出的信息如下: 



[

{

"ID": "sicei6vjehoh8hj4b64ia2esi",

"Version": {

"Index": 74589

},

"CreatedAt": "2021-03-11T07:02:22.774283061Z",

"UpdatedAt": "2021-03-11T07:02:22.774283061Z",




 


"Spec": {

"Name": "accounts_secret_config",

"Labels": {

"com.docker.stack.namespace": "accounts"

}

}

}

]





查看Swarm中的服务列表,命令如下: 



docker service ls





运行命令后输出的内容如下(前4列): 



ID NAMEMODE REPLICAS

9iqolp4cb3jj accounts_apireplicated 1/1

rwptwtkorrhl accounts_db-migration replicated 0/1

0v6re1vdchf4 accounts_mysqlreplicated 1/1

sx7b95d9au5q accounts_webreplicated 1/1





后两列内容如下: 



IMAGE PORTS

bitmyth/accounts:v1.0.0 *:8081->80/tcp

goose:latest

mysql:5.7

bitmyth/accounts-frontend:v1.0.0 *:8088->80/tcp





4. 查看服务状态
accounts_api服务启动后,可以请求它所提供的注册接口,以此来验证是否工作正常,命令如下: 



curl -v localhost:8081/v1/register -d'{"name":"test"}'





如果服务正常运行,则会收到类似响应,响应内容如下: 



{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjQsInVzZXIiOnRydWUsImV4cCI6MTYxNT
U2MjA5NywiaWF0IjoxNjE1Mzg5Mjk3LCJpc3MiOiJCaWthc2gifQ.USttmL2zRAzlLzNb30dBu7txcWn5NLHzMh88
d4-ybBc","user":{"ID":4,"Name":"test","Password":"","Email":"","Phone":"","Avatar":"","CreatedAt":"2021-03-10T15:14:57.870701764Z","UpdatedAt":"2021-03-10T15:14:57.870701764Z","DeletedAt":null}}





accounts_api服务是无状态的,所以可以很方便地把accounts_api服务实例扩展到3个,命令如下: 



docker service scale accounts_api=3





运行命令后输出的内容如下: 



accounts_api scaled to 3

overall progress: 3 out of 3 tasks

1/3: running [==================================================>]

2/3: running [==================================================>]

3/3: running [==================================================>]

verify: Service converged





以上内容表明全部启动成功,说明Docker configs可以让不同的节点共享。
查看accounts_api服务的容器实例分布在哪些节点,命令如下: 



docker service ps accounts_api





运行命令后输出的内容如下(前4列): 



ID NAME IMAGE NODE

jd0p13e9s39r accounts_api.1 bitmyth/accounts:v1.0.0 ip-172-31-30-250

7zy6mq1vfkrz accounts_api.2 bitmyth/accounts:v1.0.0 ip-172-31-35-212

tdf2qiithyz4 accounts_api.3 bitmyth/accounts:v1.0.0 ip-172-31-32-49





后4列内容如下: 



DESIRED STATECURRENT STATE ERROR PORTS

Running Running 9 minutes ago

Running Running 4 minutes ago

Running Running 4 minutes ago





5. 关闭应用
关闭整个应用,命令如下: 



docker stack rm accounts





运行命令后输出的信息如下: 



Removing service accounts_api

Removing service accounts_db-migration

Removing service accounts_mysql

Removing service accounts_web

Removing secret accounts_secret_config

Removing config accounts_plain_config

Removing network accounts_default





5.3.9约束服务调度
在生产环境中Swarm集群通常有多个节点。在调度下,服务会在多节点间转移。这样依赖本地数据卷的服务在调度到其他节点继续运行时,就会面临数据丢失的情况。例如MySQL服务,在前面定义的stack.yaml文件中,MySQL使用了一个本地数据卷。MySQL的数据会保存到这个数据卷中。如果MySQL被调度到了其他节点,则在其他节点上就无法找到之前保存的数据。
这个问题可以通过指定MySQL服务运行在固定的节点来解决。也就是在调度过程中限制MySQL服务,使它只能调度到满足要求的节点。这个功能在Swarm中称为放置约束(Placement Constraints)。顾名思义就是对把服务放到哪个节点运行进行约束。
放置约束条件通过对节点属性进行筛选实现。支持通过匹配节点的标签(node.labels)实现,也可以通过匹配节点的角色(node.role)、节点ID(node.id)及节点的主机名(node.hostname)等实现。
如果使用标签实现放置约束,则需要定义标签并为节点设置标签,然后在服务定义中加上需要匹配的节点标签作为限制条件,这样服务就会调度到和标签匹配的节点上。
只有节点拥有的标签和服务放置约束中的指定的标签匹配。服务才会被调度成功。指定了放置限制的服务,如果在调度时没有节点拥有匹配的标签,则服务将无法调度,会一直处于待处理状态。如果节点无法满足服务的其他放置约束条件,则服务同样无法调度。
Swarm集群中节点的标签格式为key=value。可以为节点指定一个或多个标签。
下面使用标签作为MySQL服务的放置约束。假设MySQL服务可以运行的节点需要有标签role=db,修改stack.yaml文件中MySQL服务的定义,代码如下: 



mysql:

image: mysql:5.7

deploy:

placement:

constraints:

- "node.labels.role==db"





image字段下添加的deploy字段用于指定服务部署相关的配置。deploy字段下的placement用于指定服务放置配置。placement下的constraints用于指定放置的限制条件,这里指定了标签匹配条件。约束条件为node.labels.role==db,表示部署到拥有role=db标签的节点。注意条件中的操作符是2个等号“==”。约束条件中用“==”表示匹配,用“!=”表示排除。
同样地,dbmigration服务因为挂载了本地的数据迁移文件,也需要在固定的节点执行,所以要为它指定放置约束。修改后的dbmigration服务的定义如下: 



db-migration:

image: goose

depends_on:

- mysql

working_dir: /migrations

volumes:

- ./src/database/migrations:/migrations

command: goose up

environment:

GOOSE_DRIVER: mysql

GOOSE_DBSTRING: root:${MYSQL_PASSWORD}@tcp(mysql)/${MYSQL_DB}

deploy:

restart_policy:

condition: none

placement:

constraints:

- "node.labels.role==db"





假设希望MySQL运行的节点是ip1723130250,首先为节点ip1723130250设置标签role=db,命令如下: 



docker node update --label-add role=db ip-172-31-30-250





验证标签设置是否生效,命令如下: 



docker node inspect ip-172-31-30-250





在运行命令后输出的信息中会看到Spec字段,字段如下: 



"Spec": {

"Labels": {

"role": "db"

},





表示节点ip1723130250已经拥有了标签role=db。
完成修改后,重新部署应用,命令如下: 



docker stack deploy --compose-file stack.yaml accounts





运行命令后等待部署结束,然后查看MySQL服务是否被调度到节点ip1723130250运行,命令如下: 



docker service ps accounts_mysql





运行命令后输出信息中的NODE列的内容如下: 



NODE

ip-172-31-30-250






说明放置约束生效了。MySQL服务运行在包含role=db标签的节点ip1723130250上。
5.3.10日志收集
1. 简介

日志对于观测应用运行情况,定位和修复故障及系统运行状态监控等起着重要作用。Swarm中运行的服务如果有多个容器实例,则查看服务所产生的日志可以使用docker service logs命令。它会把服务中所有容器的日志集中起来显示,但是使用docker service logs命令查看日志的方式在搜索时不方便,并且需要登录到服务器上操作。
常用的解决方案是用专门的收集工具把容器日志转发到搜索引擎。下面介绍如何使用EFK(Elasticsearch+Fluentd+Kibana)工具栈实现日志收集和处理。
2. Fluentd
Fluentd是开源的日志数据收集工具,使用简单并且性能优异。支持对日志进行自定义格式处理和转发到自定义的日志处理服务。Fluentd对Docker的支持很好,是Docker内置的日志驱动之一。Fluentd是CNCF(云原生计算基金会)已完成项目之一。
Fluentd同时具有良好的扩展性和灵活性,支持使用自定义插件来满足个性化需求。
EFK技术栈中Fluentd需要把收集的日志转发到Elasticsearch,所以要为Fluentd安装Elasticsearch插件,而Fluentd官方的镜像没有安装Elasticsearch插件,需要基于官方镜像构建自定义的镜像。下面开始构建Fluentd镜像,所需的Dockerfile如下: 



FROM fluent/fluentd:v1.12.0-debian-1.0



USER root



RUN ["gem", "install", "fluent-plugin-elasticsearch", "--no-document", "--version", "4.3.3"]



USER fluent





在这个Dockerfile同级目录下运行命令,以便构建镜像,命令如下: 



docker build -t bitmyth/fluentd-es:v1.0.0 .





这样就拥有了安装了Elasticsearch插件的Fluentd镜像。接着把这个镜像上传到Docker Hub。这样集群中的节点就可以下载这个镜像了,命令如下: 



docker push bitmyth/fluentd-es:v1.0.0 .





使用Fluentd收集Swarm集群中的所有容器的日志,需要将Fluentd设置为Swarm全局服务。修改stack.yaml文件,增加Fluentd服务的定义,代码如下: 



fluentd:

image: bitmyth/fluentd-es:v1.0.0

environment:

FLUENTD_CONF: 'fluent.conf'

configs:

- source: fluentd_config

target: /fluentd/etc/fluent.conf

ports:

- "24224:24224"

- "24224:24224/udp"

deploy:

mode: global





下面介绍指令的含义。
environment字段中定义了FLUENTD_CONF环境变量,把它的值设置为fluent.conf。这个环境变量用于指定Fluentd读取配置的文件名,代码如下: 



environment:

FLUENTD_CONF: 'fluent.conf'





configs字段中定义了fluentd_conf,target: /fluentd/etc/fluent.conf表示把配置fluentd_config挂载到容器中的/fluentd/etc/fluent.conf文件。Fluentd默认的配置文件夹是/fluentd/etc/,这样Fluentd就可以读取到自定义的fluent.conf文件中,代码如下: 



configs:

- source: fluentd_config

target: /fluentd/etc/fluent.conf





公开Fluentd监听的TCP端口24224和UDP端口24224,代码如下: 



ports:

- "24224:24224"

- "24224:24224/udp"





服务将以global模式部署。也就是在所有节点都会启动一个Fluentd容器实例。这样Fluentd就可以收集到全部节点上运行的容器所产生的日志。



deploy:

mode: global





上述Fluentd定义中用到的fluentd_config定义如下: 



configs:

fluentd_config:

file: ./config/fluentd/fluent.conf





表明fluentd_config配置项的内容将从./config/fluentd/fluent.conf文件中读取。Fluentd默认将文件的编码配置为UTF8或ASCII。
./config/fluentd/fluent.conf文件内容如下: 



<source>

@type forward

port 24224

bind 0.0.0.0

</source>



<match *.**>

@type copy



<store>

@type elasticsearch

host elasticsearch

port 9200

logstash_format true

logstash_prefix fluentd

logstash_dateformat %Y%m%d

include_tag_key true

type_name access_log

tag_key @log_name

flush_interval 1s

</store>



<store>

@type stdout

</store>

</match>





下面介绍配置文件中的指令含义。
定义日志的输入源,也就是数据的来源。Fluentd配置文件中支持定义多个source。每个source都必须指定@type参数来表明使用什么输入插件。@type forward表示处理输入的插件类型是转发。除了forward,Fluentd还支持http插件。http插件会提供HTTP服务,接收以HTTP发送的数据,而forward提供TCP服务,接收以TCP数据包的形式发送的数据。当然这两个输入插件可以同时启用。如果内置的输入插件无法满足需求,Fluentd支持开发自定义输入插件。
指令port 24224表示Fluentd通过TCP端口24224接收日志数据,bind 0.0.0.0表示监听任意的网卡设备。
Fluentd中接收的数据会被包装成一个事件。事件包含了tag属性、time属性和record属性。tag属性用于识别日志数据的来源。它是用英文句点“.”分隔的字符串。Fluentd推荐在tag中使用的字符是小写的字母、数字及下画线,用正则表达式表示为^[az09_]+$。事件的time属性是UNIX时间格式,由输入插件指定它的值。事件的record属性是一个JSON对象,包含了实际输入的数据,代码如下:



<source>

@type forward

port 24224

bind 0.0.0.0

</source>





指令match用于控制Fluentd如何匹配和输出数据。match *.**表示匹配拥有*.**格式的tag(标签)的事件。每个match指令都必须指定一个匹配模式和一个@type参数。匹配模式用于匹配事件的标签。match中的@type用于指定使用什么输出插件。
这里的@type copy指定了copy插件。它是Fluentd内置的一个标准输出插件。copy会把事件复制到多个输出。
指令match下包含的store指定了存储输出的目的地。match指令中至少需要指定一个store,代码如下: 



<match *.**>

@type copy





store指令下的@type elasticsearch用于指定使用elasticsearch插件。host用于指定elasticsearch的IP地址,因为Swarm中的服务可以通过DNS来解析它的IP地址,所以这里使用elasticsearch服务名作为host字段的值。port 9200指定elasticsearch服务的TCP端口号为9200。如果需要发送到多个elasticsearch服务,则可以通过hosts指定,例如: hosts:host1:port1,host2:port2。通过示例中这种格式指定了多个elasticsearch服务的情况下,指定单个elasticsearch服务的host和port字段会被忽略,代码如下: 



<store>

@type elasticsearch

host elasticsearch

port 9200

logstash_format true

logstash_prefix fluentd

logstash_dateformat %Y%m%d

include_tag_key true

type_name access_log




 


tag_key @log_name

flush_interval 1s

</store>





采用与Logstash兼容的格式把数据写入索引,代码如下: 



logstash_format true





将索引的前缀设置为fluentd,代码如下: 



logstash_prefix fluentd





将索引名字中的日期格式设置为%Y%m%d,例如20210325,代码如下: 



logstash_dateformat %Y%m%d





将Fluentd的tag加入Elasticsearch数据中,代码如下: 



include_tag_key true





指定写入Elasticsearch中的文档类型的名字。type_name参数在Elasticsearch 7中会设置为固定值_doc,而Elasticsearch 8会忽略这个参数,代码如下: 



type_name access_log





指定写入Elasticsearch中的Fluentd tag的键名为@log_name,代码如下: 



tag_key @log_name





指定将缓存数据写入目的地的时间间隔为1s,代码如下: 



flush_interval 1s





3. Elasticsearch
Elasticsearch是非常流行的开源搜索引擎,在EFK技术栈中的角色是保存和索引Fluentd收集的日志数据。下面定义Elasticsearch服务,修改stack.yaml文件,代码如下: 



elasticsearch:

image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2

environment:

- "discovery.type=single-node"

ports:

- "9200:9200"

deploy:




 


resources:

limits:

cpus: '2'

memory: 2G

reservations:

cpus: '1'

memory: 1.5G





由于Elasticsearch服务需要使用比较多的内存,所以在上述定义中增加了对CPU和内存资源使用限制的声明。
限制Elasticsearch最多使用2个CPU和2GB的内存。在不限制容器使用内存的情况下,如果容器使用了过多的系统内存,则会被内核强制中止运行,代码如下: 



limits:

cpus: '2'

memory: 2G





为Elasticsearch服务预留1个CPU和1.5GB内存。如果没有节点可以满足指定的计算资源需求,则服务将无法调度成功,代码如下: 



reservations:

cpus: '1'

memory: 1.5G





4. Kibana
Kibana为使用Elasticsearch提供了友好的UI界面,降低了使用Elasticsearch的难度。方便用户查询Elasticsearch中的数据。
下面定义Kibana服务,修改stack.yaml文件,代码如下: 



kibana:

image: kibana:7.10.1

depends_on:

- "elasticsearch"

ports:

- "5601:5601"





depends_on声明的Kibana服务依赖于Elasticsearch,当Elasticsearch启动后才会启动Kibana。
5. 使用Fluentd日志驱动
现在修改第1章介绍的用户认证应用中包含的服务所用的日志驱动。首先修改后端项目,也就是api服务,设置它的日志驱动,代码如下: 



api:

logging:

driver: "fluentd"

options:

fluentd-address: localhost:24224

tag: api.{{.Name}}





其他部分保持不变,增加了logging设置。将日志驱动设置为fluentd,日志驱动选项通过options定义。options下的fluentdaddress定义了fluentd监听的网络地址。由于Fluentd是以global模式部署的,所以每个节点都可以通过localhost这个地址访问它。options下的tag定义了日志的名字。{{.Name}}会被替换成容器的名字。最后api.{{.Name}}会变成类似api.accounts_api.1.gvq4cxpd99fdt7grxkr38zle4这样的字符串。
同样地,可为前端项目(Web服务)设置日志驱动,修改后的代码如下: 



web:

logging:

driver: "fluentd"

options:

fluentd-address: localhost:24224

tag: web.{{.Name}}





Web服务定义的其他部分保持不变,增加了logging设置。
6. 部署
增加与日志相关的设置后,重新将应用部署到Swarm集群。首先设置环境变量,命令如下: 



export $(cat .env.prod)





这样就可以使在.env文件中定义的全部环境变量在当前的shell进程中生效。完成环境变量设置后,再将应用部署到生产环境的Swarm集群中,命令如下: 



docker stack deploy --compose-file stack.yaml accounts





运行命令后输出的内容如下: 



Creating network accounts_default

Creating secret accounts_secret_config

Creating config accounts_fluentd_config

Creating config accounts_plain_config

Creating service accounts_fluentd

Creating service accounts_elasticsearch

Creating service accounts_kibana

Creating service accounts_web




 


Creating service accounts_api

Creating service accounts_mysql

Creating service accounts_db-migration





查看Fluentd服务的状态,命令如下: 



docker service ps accounts_fluentd





运行命令后输出信息的后5列如下: 



NODE DESIRED STATECURRENT STATE ERROR PORTS

ip-172-31-35-212 Running Running 3 minutes ago

ip-172-31-32-49Running Running 3 minutes ago

ip-172-31-30-250 Running Running 3 minutes ago





可以看到Swarm集群中所有的节点运行着Fluentd服务。
接下来打开浏览器访问Kibana,设置要查看的索引的模式。默认首页如图51所示。


图51Kibana Index patterns首页


单击页面中的Create index pattern按钮,会跳转到如图52所示页面。


图52创建Kibana index patterns页


在页面中Index pattern name输入框中输入fluentd*,如图53所示。


图53输入Index pattern name


然后单击Next step按钮。在新页面中选择Time field下拉列表框中的@timestamp,之后单击Create index pattern按钮,如图54所示。



图54选择Time field


接下来单击页面左上角菜单,在展开的菜单中选中Discover,如图55所示。


图55单击Discover链接


现在就可以在页面中观察日志了。访问前端项目,完成注册和登录,这样可以产生一些日志数据,然后回到Kibana中查看这些日志数据。Kibana会展示最新的日志,如图56所示。


图56查看日志


在日志中可以看到前端项目和后端项目的日志。以前端项目产生的日志为例,如图57所示。



图57前端日志示例


日志记录的字段如表51所示。


表51前端日志字段表



字段名字段值

@log_nameweb.accounts_web.1.gvq4cxpd99fdt7grxkr38zle4
@timestampMar 25, 2021 @ 16: 51: 04.000
_idRaCUaHgBvNUrEt1ffJ79
_indexfluentd20210325
_score
_type_doc
container_id747e096cabfd7f94e05c734776d3eac03396e865c0cc9d07c5d1df24729f8bd7
container_name/accounts_web.1.gvq4cxpd99fdt7grxkr38zle4
log10.0.0.2   [25/Mar/2021:08:51:04 +0000] "GET / HTTP/1.1" 200 904 """"""
sourcestdout


可以从container_name字段判断出日志产生自哪个服务,通过container_id可以准确定位产生日志的容器。
7. 总结
基于EFK技术栈实现的日志收集系统可以实时地收集日志,并且可以方便地检索日志,以及定位故障,从而实现集群分布式环境下容器日志的统一处理。