1. 前言

为了加深自己的理解,方便以后针对公司项目提出优化建议,本文将从头开始根据官方文档简要的学习一下Dokcer。

关于Docker的部署安装,本文将直接略过

2. 容器

2.1 定义开发环境

过去,如果你要部署一个python程序,需要将应用copy到服务器上,并且下载源码包,进行编译,pip,运行。

期间可能会遇到各种各样的报错,依赖问题。 而docker的出现,正好解决了这个痛点。我们只需要编写一个Dockerfile即可。

2.2 编写Dockerfile

在本机新建一个空目录,并且CD进去。注意一个Dockerfile尽量单独占用一个目录。

1
2
3
4
5
[root@localhost ~]# mkdir flask
[root@localhost ~]# cd flask/
[root@localhost flask]# touch Dockerfile
[root@localhost flask]# ls
Dockerfile

Dockerfile 内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Use an official Python runtime as a parent image
# 每个Dockerfile必须已FROM开头作为基础镜像
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

在同级目录创建Dockerfile所需要文件

requirements.txt

1 Flask
2 Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

app.py

from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \\
"<b>Hostname:</b> {hostname}<br/>" \\
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)
if __name___ == "___main___":
app.run(host='0.0.0.0', port=80)

创建Dockerfile

1
2
3
4
5
6
7

1 [root@localhost flask]# ls
2 app.py Dockerfile requirements.txt
3 [root@localhost flask]# docker build -t friendlyhello .
4 [root@localhost flask]# docker images
5 REPOSITORY TAG IMAGE ID CREATED SIZE
6 friendlyhello latest a72fbd8c6be9 23 seconds ago 148MB

2.2 运行容器

1
docker run -p 4000:80 friendlyhello

2.3 将容器推送至私有仓库

1
2
1 docker tag a72fbd8c6be9 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1
2 docker push registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1

3. docker-compose

docker-compose.yml文件是一个YAML文件,它定义了如何Docker容器在生产中应表现。

将此文件保存在docker-compose.yml任意位置。确保已将在第1部分中创建的图像推送到仓库,并通过替换为图像详细信息进行更新。 .yml username/repo:tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<pre class="wp-block-code"><code>version: "3"
services:
web:
image: registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart\_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
networks:
webnet:

该docker-compose.yml文件告诉Docker执行以下操作:

  • 拉取我们指定仓库的镜像
  • 运行该镜像5个实例,每个容器最多使用一个cpu核心时间10%,以及内存50M
  • 如果其中一个发生故障,则立即重启
  • 将主机上的4000端口映射到容器80
  • 指定容器使用的网络
  • 创建网络webnet,使用默认的网络模型 及负载均衡

3.1 运行负载均衡

1
2
3
4
5
6
1 [root@localhost ~]# docker swarm init
2 Swarm initialized: current node (1b0q7xu7uuila4soswobf4rpo) is now a manager.
3
4 To add a worker to this swarm, run the following command:
5
6 docker swarm join --token SWMTKN-1-37347og8902vpkeqelo2dolz2xjy2fshsyy6dcnam6nke3ejof-98vgwa33r3c03bonp09vo94wp 192.168.199.107:2377

现在运行它。您需要给您的应用命名。在这里,它设置为 getstartedlab

1
docker stack deploy -c docker-compose.yml getstartedlab

获取我们应用程序中一项服务的服务ID:

1
2
3
1 [root@localhost ~]# docker service ls
2 ID NAME MODE REPLICAS IMAGE PORTS
3 mi0br9jqcqk9 getstartedlab\_web replicated 5/5 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 \*:4000->80/tcp

在服务中运行的单个容器称为任务。为任务分配了唯一的ID,这些ID会按数字递增,直到replicas您在中定义 的数量为止docker-compose.yml。列出您的服务任务:

1
2
3
4
5
6
7
1 [root@localhost ~]# docker service ps getstartedlab_web
2 ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
3 0hdcuaw94cj1 getstartedlab\_web.1 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 localhost.localdomain Running Running 4 minutes ago
4 xaacomx3zz9b getstartedlab\_web.2 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 localhost.localdomain Running Running 4 minutes ago
5 4eusckdikayz getstartedlab\_web.3 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 localhost.localdomain Running Running 4 minutes ago
6 8aomhu4el5ql getstartedlab\_web.4 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 localhost.localdomain Running Running 4 minutes ago
7 sgqjosutb9t8 getstartedlab\_web.5 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 localhost.localdomain Running Running 4 minutes ago

3.2 验证

此时,我们打开浏览器访问4000端口并且刷新验证,可以清楚的看到Hostname在动态的变化中,这说明我们的负载均衡网络已经起了作用

3.3 缩减集群

您可以通过更改docker-compose.yml中的replicas值,保存更改并重新运行docker stack deploy命令来缩减应用程序:

1
2

docker stack deploy -c docker-compose.yml getstartedlab

Docker执行就会自动更新,无需删除群集或杀死任何容器。

现在,重新运行docker container ls -q 以查看已重新配置已部署的实例。如果按比例扩大副本,则会启动更多任务,从而启动更多容器。

3.4 删除应用和群集

  • 使用 docker stack rm 已关闭容器
    1
    2
    3
    1 [root@localhost ~]# docker stack rm getstartedlab
    2 Removing service getstartedlab\_web
    3 Removing network getstartedlab\_webnet
    移除swarm
    1
    docker swarm leave --force

4. docker swarm 群集

swarm是一组运行Docker并加入集群的机器。在发生这种情况之后,您将继续运行您惯用的Docker命令,但是现在它们由集群管理器在集群上执行。群集中的计算机可以是物理的也可以是虚拟的。加入一个群体之后,它们被称为节点。

群管理器可以使用多种策略来运行容器,例如“最空的节点”-用容器填充利用率最低的机器。或“全局”,它可以确保每台机器恰好获得指定容器的一个实例。您可以指示群管理器在Compose文件中使用这些策略,就像您已经在使用的策略一样。

群集管理器是群集中唯一可以执行命令或授权其他计算机作为工作人员加入群集的计算机。工人只是在那里提供能力,而无权告诉其他任何机器它可以做什么和不能做什么。

到目前为止,您一直在本地计算机上以单主机模式使用Docker。但是Docker也可以切换到swarm模式,这就是启用swarms的原因。立即启用群集模式会使当前计算机成为群集管理器。从那时起,Docker将在您管理的集群上运行您执行的命令,而不仅仅是在当前机器上运行。

4.1 设置 swarm 群集

群由多个节点组成,这些节点可以是物理机也可以是虚拟机。基本概念非常简单:运行docker swarm init以启用集群模式,并使当前计算机成为集群管理器,然后docker swarm join在其他计算机上运行 以使它们作为工作组加入集群。在下面选择一个标签,查看在各种情况下如何播放。我们使用虚拟机来快速创建一个两机集群,并将其变成一个集群。

1
2
3
4
5
[root@localhost ~]# docker swarm init  --advertise-addr 192.168.199.107
Swarm initialized: current node (b3n0ugrgd2kc8s7pj0l028rt2) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3jcpnxc6nb7bmdiitm32pfqmdokhldoduemeh44cxvkymai46m-3mpb6pf2wi1dthd6nrqd97m68 192.168.199.107:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
  • 默认监听两个端口,tcp2377端口为集群的管理端口,tcp7946为节点之间的通讯端口
  • 默认会创建一个overlay的网络ingress,还会创建一个桥接的网络 docker_gwbridge
  • 入口网络默认监听端口为 UDP/4789

4.2 加入群集

1
2
3
4
5
6
7
1 [root@localhost ~]# docker swarm join --token SWMTKN-1-3jcpnxc6nb7bmdiitm32pfqmdokhldoduemeh44cxvkymai46m-3mpb6pf2wi1dthd6nrqd97m68 192.168.199.107:2377
2 This node joined a swarm as a worker.
查看集群节点
[root@localhost ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
3bm6nvegbgh9gpf7b738c81w4 localhost.localdomain Ready Active 18.09.6
b3n0ugrgd2kc8s7pj0l028rt2 \* localhost.localdomain Ready Active Leader 18.09.6

4.3 在管理节点中部署应用

可以使用在第2部分中 docker stack deploy 相同命令部署本地副本,使用其作为集群管理器的功能来部署应用程序docker-compose.yml。此命令可能需要几秒钟才能完成,并且部署需要一些时间才能使用。docker service ps 在群集管理器上使用 命令来验证是否已重新部署所有服务。

1
2
3
[root@localhost ~]# docker stack deploy -c docker-compose.yml getstartedlab
Creating network getstartedlab_webnet
Creating service getstartedlab_web

查看 service 的部署情况

1
2
3
[root@localhost ~]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
cs1ba0jhs0xn getstartedlab\_web replicated 2/5 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 \*:4000->80/tcp

等待后再次查看,发现已经部署完成

1
2
3
[root@localhost ~]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
cs1ba0jhs0xn getstartedlab\_web replicated 5/5 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 \*:4000->80/tcp

查看 service 详情

1
docker stack ps getstartedlab

4.4 集群拓扑

如下图拓扑,在swarm前端,集群默认采用负载均衡在分配请求的流量,无论从哪个节点去访问服务,都会转发到后端某一容器中

你不需要关心在集群中你的服务身在何处.

4.5 参考命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux)
docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10
docker-machine env myvm1 # View basic information about your node
docker-machine ssh myvm1 "docker node ls" # List the nodes in your swarm
docker-machine ssh myvm1 "docker node inspect <node ID>" # Inspect a node
docker-machine ssh myvm1 "docker swarm join-token -q worker" # View join token
docker-machine ssh myvm1 # Open an SSH session with the VM; type "exit" to end
docker node ls # View nodes in swarm (while logged on to manager)
docker-machine ssh myvm2 "docker swarm leave" # Make the worker leave the swarm
docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm
docker-machine ls # list VMs, asterisk shows which VM this shell is talking to
docker-machine start myvm1 # Start a VM that is currently not running
docker-machine env myvm1 # show environment variables and command for myvm1
eval $(docker-machine env myvm1) # Mac command to connect shell to myvm1
& "C:\\Program Files\\Docker\\Docker\\Resources\\bin\\docker-machine.exe" env myvm1 Invoke-Expression # Windows command to connect shell to myvm1
docker stack deploy -c <file> <app> # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file
docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app)
docker-machine ssh myvm1 "docker stack deploy -c <file> <app>" # Deploy an app using ssh (you must have first copied the Compose file to myvm1)
eval $(docker-machine env -u) # Disconnect shell from VMs, use native docker
docker-machine stop $(docker-machine ls -q) # Stop all running VMs
docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images

5. 扩展集群-stack

堆栈是一组共享依赖关系的相互关联的服务,可以一起进行整理和扩展。单个堆栈能够定义和协调整个应用程序的功能(尽管非常复杂的应用程序可能要使用多个堆栈)。

一些好消息是,从第3部分开始,当您创建Compose文件并使用时,从技术上讲您一直在使用堆栈docker stack deploy。但这是在单个主机上运行的单个服务堆栈,通常不会在生产环境中发生。在这里,您可以学到的知识,使多个服务相互关联,然后在多台计算机上运行它们。

5.1 添加服务并重新启动

  1. docker-compose.yml在编辑器中打开并将其内容替换为以下内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    version: "3"
    services:
    web:
    image: registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1
    deploy:
    replicas: 5
    resources:
    limits:
    cpus: "0.1"
    memory: 50M
    restart\_policy:
    condition: on-failure
    ports:
    - "4000:80"
    networks:
    - webnet
    visualizer:
    image: dockersamples/visualizer:stable
    ports:
    - "8080:8080"
    volumes:
    - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
    placement:
    constraints: \[node.role == manager\]
    networks:
    - webnet
    networks:
    webnet:

    唯一增加的服务就是跟 WEB同级的 visualizer,这个是Docker的开源图形化容器,其中比较重要的是,volumes,一定要挂载docker.sock,容器将调用此套接字获取集群的状态

  2. 在管理器上重新运行命令 docker stack deploy,所有需要更新的服务都会更新:

    1
    2
    3
    [root@localhost ~]# docker stack deploy -c docker-compose.yml flask
    Updating service flask_web (id: w1p5y92fzz920phb5cwqir2l8)
    Creating service flask_visualizer
  3. 查看可视器

    可视化工具是一项独立的服务,可以在将其包含在堆栈中的任何应用中运行。它不依赖其他任何东西。现在让我们创建一个服务会有依赖性:Redis的服务,提供访客计数器。

5.2 数据持久化

让我们再次经历相同的工作流程,以添加用于存储应用程序数据的Redis数据库。

  1. 保存此新docker-compose.yml文件,最终添加一个Redis服务。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    version: "3"
    services:
    web:
    image: registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1
    deploy:
    replicas: 5
    restart\_policy:
    condition: on-failure
    resources:
    limits:
    cpus: "0.1"
    memory: 50M
    ports:
    - "80:80"
    networks:
    - webnet
    visualizer:
    image: dockersamples/visualizer:stable
    ports:
    - "8080:8080"
    volumes:
    - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
    placement:
    constraints: \[node.role == manager\]
    networks:
    - webnet
    redis:
    image: redis
    ports:
    - "6379:6379"
    volumes:
    - "/home/docker/data:/data"
    deploy:
    placement:
    constraints: \[node.role == manager\]
    command: redis-server --appendonly yes
    networks:
    - webnet
    networks:
    webnet:
    要使Redis的数据持久化,首先要保证数据能落地到本地并且保存,所以我们需要在本机挂载data目录,容器来回移动时,存储在本机的数据将会保留,从而实现连续性。

并且我们要保证redis启动和重启必须始终位于相同的节点,否则数据将会不同步,constraints: [node.role == manager]

  1. 在管理节点创建Redis数据目录
    1
    [root@localhost ~]# mkdir -p /home/docker/data
    3 重新部署应用
    1
    2
    3
    4
    [root@localhost ~]# docker stack deploy -c docker-compose.yml flask
    Updating service flask_visualizer (id: nilgi5vx3dh12kw7q0cwl4chc)
    Creating service flask_redis
    Updating service flask_web (id: w1p5y92fzz920phb5cwqir2l8)
  2. 检查service是否正常运行
    1
    2
    3
    4
    5
    [root@localhost ~]# docker service ls
    ID NAME MODE REPLICAS IMAGE PORTS
    mxvddks7lpbc flask\_redis replicated 1/1 redis:latest \*:6379->6379/tcp
    nilgi5vx3dh1 flask\_visualizer replicated 1/1 dockersamples/visualizer:stable \*:8080->8080/tcp
    w1p5y92fzz92 flask\_web replicated 5/5 registry.cn-hangzhou.aliyuncs.com/momom/pythonflask:v0.1 \*:4000->80/tcp
  3. 验证计数器是否工作

    另外,检查任一节点IP地址上端口8080上的可视化程序,并注意该redis服务与web和visualizer服务一起运行。

6. 结尾

到这里已经简单了解了docker的部署,负载,以及群集的使用,后面将会逐个详细的介绍各组件。