为啥学Docker?
- 主要是现有的工作上需要用到docker,经常去部署,繁琐的安装浪费太多的时间
- docker是人人必须得会的,是当代互联网人人必须要掌握的工具
一、Docker安装
1.1 windows安装
(1)BOIS 必须开启虚拟化,intel和AMD以及主板多样化,自行百度。
(2)windows开启Hyper-v:https://docs.microsoft.com/zh-cn/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v winodws10默认开启
(3)https://www.docker.com/
双击桌面Docker图标,任务栏会出现下图图标:
报错了:
原因是windows运行linux内核系统需要安装Linux 内核更新包
1.下载linux内核更新包:[在 Windows 10 上安装 WSL | Microsoft Docs](
下载地址:https://docs.microsoft.com/zh-cn/windows/wsl/install-win10#step-4---download-the-linux-kernel-update-package
)
2.设置默认wsl默认版本
用系统管理员的角色打开windows的powershell,然后运行如下命令:
wsl --set-default-version 2
重启电脑,再运行docker就正常了
打开powershell 验证 具有client 和 server 表示成功:
1.2 Liunx 安装
执行命令
# 下载sh安装脚本
curl -fsSL get.docker.com -o get-docker.sh
# 执行脚本
sh get-docker.sh
# 验证是否安装成功(client、server)
sudo docker version
# 启动server服务
sudo systemctl start docker
shell脚本详情:github.com/docker/dock…
- 非root用户需要添加到docker用户组内
sudo groupadd docker #添加docker用户组
sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中
newgrp docker #更新用户组
docker ps #测试docker命令是否可以使用sudo正常使用
- centos更改docker默认路径,随着使用存储空间会提示不足
mkdir -p /etc/systemd/system/docker.service.d/ && mkdir -p /home/docker/lib/docker
vim /etc/systemd/system/docker.service.d/devicemapper.conf
录入以下配置文件信息
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --graph=/home/docker/lib/docker
重启docker
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# systemctl enable docker
二、容器使用
2.1 初识Docker CLI命令行
# 显示版本号命令
docker version
# 当前docker基本环境状态
docker info
# 显示docker命令用法
docker
docker 关键词 --help
2.2 镜像和容器
镜像概念:
- Docker image是一个
read-only文件 - 这个文件包含文件系统,源码,库文件,依赖,工具等一些运行application所需要的文件
- 可以理解成一个模板
- docker image具有分层的概念
容器概念:
- “一个运行中的docker image”
- 实质是复制image并在image最上层加上一层
read-write的层 (称之为container layer,容器层) - 基于同一个image可以创建多个container
镜像获取方式:
- 自己制作
- 从网上拉取 如:docker hub
2.3 创建第一个容器
# 创建容器
docker container run nginx 简写:docker run 镜像
# 查看运行的容器
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cfbcdc3f778f nginx "/docker-entrypoint.…" About a minute ago Up About a minute 80/tcp mystifying_hypatia
# 停止容器
docker container stop 容器NAME或ID 简写:docker stop 镜像
# 当前运行或停止的所有容器
docker container ps -a 或 docker container ls -a 简写:docker ps -a
备注:ps的话是早期版本的命令
# 删除容器
docker container rm 容器ID或NAME 简写:docker rm
# 删除所有停止的容器
docker system prune -f
大部分命令都可以省略container,建议都加上,这样能够清楚是在操作容器还是镜像等
| 操作 | 命令(全) | 命令(简) | |||
|---|---|---|---|---|---|
| 容器的创建 | docker container run “image name” | docker run “image name” | |||
| 容器的列出(up) | docker container ls | docker ps | |||
| 容器的列出(up和exit) | docker container ls -a | docker ps -a | |||
| 容器的停止 | docker container stop “name or ID” | docker stop “container name or ID” | |||
| 容器的删除 | docker container rm “name or ID” | docker rm “container name or ID” |
2.4 docker命令行技巧之全部停止或启动,运行容器强制删除
# 全部停止容器
docker container stop $(docker container ps -aq)
# 全部删除容器
docker container rm $(docker container ps -aq)
# 运行容器强制删除
常规是先停止运行然后删除,以下命令可以在运行中强行删除
docker container rm ID -f
2.5 容器的attached和detached模式
前台执行为attached,能看到程序运行的信息 (不推荐前台运行)
如:docker container run -p 80:80 nginx
后台运行就称之为:detached模式 ··· 如: docker container run -d -p 80:80 nginx ··· 那么后台运行了如何查看运行log呢?
docker attach 容器ID (不推荐前台查看)
2.6 容器交互模式
- 查看后台运行容器LOG
docker container logs 容器ID
- 动态显示log
docker container logs 容器ID -f
- docker 交互式创建容器 -it
-it 参数为交互模式,此命令运行后,我们会进入shell模式,exit 退出容器交互,随之容器也会停止,
原因主要是因为COMMAND 为sh,结束后 容器也会随之停止
docker container run -it ubuntu sh
- docker exec交互方式(常用)
通过该命令可以进入容器COMMAND中,exit退出,该退出方式仅退出的是shell并不会关闭容器
docker exec -it 容器ID 命令
案例:docker exec -it 5a26 sh
2.7 容器和虚拟机的区别
- 容器其实是进程Containers are just processes
- 容器中的进程被限制了对CPU内存等资源的访问
- 当进程停止后,容器就退出了
- 查看容器进程
docker container top 容器ID
Liunx查看进程依赖关系:
pstree -halps 进程号
2.8 docker container run 运行过程
$ docker container run -d --publish 80:80 --name webhost nginx
- 1.在本地查找是否有nginx这个image镜像,但是没有发现
- 2.去远程的image registry查找nginx镜像(默认的registry是Docker Hub)
- 3.下载最新版本的nginx镜像 (nginx:latest 默认)
- 4.基于nginx镜像来创建一个新的容器,并且准备运行
- 5.docker engine分配给这个容器一个虚拟IP地址
- 6.在宿主机上打开80端口并把容器的80端口转发到宿主机上
- 7.启动容器,运行指定的命令(这里是一个shell脚本去启动nginx)
三、镜像创建管理与发布
3.1 镜像获取方式
方式一:pull from
registry (需联网) 从registry拉取
- public(公有)
- private(私有)一般为公司内网环境
方式二:build from Dockerfile (需联网) 从Dockerfile构建
方式三:load from file (需联网) 文件导入 (离线)
通过save保存为压缩文件,通过拷贝到存储设备上,复制到其它机器上,然后通过load的命令进行加载
3.2 Dockerhub网站或Quay.io网站拉取
1.必要条件:需要注册,登录镜像网址搜索镜像
2.镜像拉取
docker image # 显示镜像信息,操作信息
docker pull nginx # 旧命令
docker image pull nginx # 推荐新命令
docker image ls # 显示本地镜像信息
docker image rm 镜像ID # 删除镜像(如果容器正在使用,无法被删除,需要删除容器)
docker image prune -a # 删除没有用到的所有镜像
docker rmi $(docker images -q) # 删除所有镜像
- docker image inspect 镜像ID (显示镜像的详细信息)
"Architecture": "amd64", # 镜像支持的平台
"Os": "linux", # 镜像支持的系统
- 下载指定版本镜像
docker image pull nginx:1.20.0
- 通过quay.io网址进行拉取
3.3 镜像导出
- 导出
docker image save 镜像ID:版本号 -o 自定义一个文件名称
此时会直接导出到当前目录下可以ls查看,可以通过存储设备进行拷贝
- 导入
# -i 参数是input 输入
docker image load -i 镜像文件名称
3.4 通过Dockerfile构建镜像
- 什么是Dockerfile Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明,具有特定的语法规则
比如:需要在Centos7上安装Python,那么肯定是先要安装依赖,然后再编译安装,命令步骤较多,使用dockerfile,将这些命令整合在一个文件中,运行的时候会直接运行,需要事先将步骤集合到一个文件中
- Dockerfile基本结构
FROM ubuntu:21.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev
ADD hello.py /
CMD ["python3", "/hello.py"]
FROM:选择基础镜像 RUN:执行命令 ADD:将本地文件添加 镜像目录中 CMD:执行命令
Dockerfile 语法不止于此
3.5 镜像构建与分享
1.在本地编写好Dockerfile文件和python文件
2.镜像构建
-t:给镜像起名称:版本号,如果不加的话为最新的
docker image build -t 自定义镜像名称 镜像路径(必须有Dockerfile文件)
值得注意的是 容器执行完CMD命令后 进程结束,容器也会随之停止
3.镜像分享出去
方法一:通过docker image save 的方式分享
方法二:通过上传到dockerhub上去,需要有dockerhub 账号
定义tag标签,符合上传要求开头必须有自己的账号信息:docker image tag 镜像名称 DockeHub账户/镜像名:版本号
PS F:\docketest> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest eb6d3da2d3c3 15 minutes ago 208MB
busybox latest beae173ccac6 13 days ago 1.24MB
docker/getting-started latest 26d80cd96d69 5 weeks ago 28.5MB
nginx 1.20.0 7ab27dbbfbdf 8 months ago 133MB
PS F:\docketest> docker image tag hello 784066538/hello:1.0
PS F:\docketest> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
784066538/hello 1.0 eb6d3da2d3c3 16 minutes ago 208MB
hello latest eb6d3da2d3c3 16 minutes ago 208MB
busybox latest beae173ccac6 13 days ago 1.24MB
docker/getting-started latest 26d80cd96d69 5 weeks ago 28.5MB
nginx 1.20.0 7ab27dbbfbdf 8 months ago 133MB
PS F:\docketest> docker image rm hello:latest
Untagged: hello:latest
PS F:\docketest> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
784066538/hello 1.0 eb6d3da2d3c3 16 minutes ago 208MB
busybox latest beae173ccac6 13 days ago 1.24MB
docker/getting-started latest 26d80cd96d69 5 weeks ago 28.5MB
nginx 1.20.0 7ab27dbbfbdf 8 months ago 133MB
PS F:\docketest>
- 登录:docker login
- 上传 docker image push
4.镜像拉取
docker image pull 账号ID/镜像名称:版本
3.6 通过commit创建镜像(保存已修改后的镜像)
通过commit对容器创建一个新的镜像
情景一:当在一个容器中安装了一些必要的环境时,可以保存为镜像,避免下次重复操作
情景二:当对一个容器进行了修改文件的操作,需要将该容器保存为镜像,案例中,我修改了nginx的访问页面
docker container run 3e4b -d -p 80:80 nginx # 启动容器
docker container exec -it 容器ID sh # 进入容器shell模式 修改了/usr/share/nginx/html/index.html 文件内容
docker container commit 容器ID 自定义镜像名称
其他知识:
docker image history 镜像 # 查看镜像分层
四、Dockerfile全面指南
构建dockerfile命令:docker image build -f dockerfile名称 -t 镜像名称 dockerfile所在本机路径 当前目录为.
4.1 基础镜像选择
构建Dockerfile文件时,首先需要FROM 一个镜像,关于镜像选择会遵循以下三点:
- 官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选择Dockerfile开源的
- 固定版本tag而不是每次都使用latest
- 尽量选择体积小的镜像 当我们选择镜像时,比如nginx,tag可以看到有last、stable、perl、alpine等,最新的、稳定的、体积小。体积小的镜像如果安装其他的软件可能会引发问题
Alpine 的意思是“高山的”,比如 Alpine plants高山植物,Alpine skiing高山滑雪、the alpine resort阿尔卑斯山胜地。
alpine系统特点
- 小巧:基于Musl libc和busybox,和busybox一样小巧,最小的Docker镜像只有5MB;
- 安全:面向安全的轻量发行版;
- 简单:提供APK包管理工具,软件的搜索、安装、删除、升级都非常方便。
- 适合容器使用:由于小巧、功能完备,非常适合作为容器的基础镜像。
4.2 RUN 执行指令
推荐只写一次RUN,通过 && \ 进行命令分隔执行,减少分层和体积 案例:
FROM ubuntu:21.04
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
4.3 文件复制和目录操作 (ADD,COPY,WORKDIR)
COPY 和 ADD 都可以把local的一个文件复制到镜像里,如果目标目录不存在,则会自动创建
推荐用法:
- ADD 适用于本地单个文件添加到一个镜像目录中
FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py
- COPY 适用于本地压缩包添加到一个镜像目录中,需要自动解压缩的场合使用 ADD
FROM python:3.9.5-alpine3.13
ADD hello.tar.gz /app/
- WORKDIR 目录切换,类似于cd命令,如果目标目录不存在会自动创建,下边案例表示本地的hello.py会添加到/app/hello.py中,适用于镜像比较复杂目录
FROM python:3.9.5-alpine3.13
WORKDIR /app
COPY hello.py hello.py
4.4 构建参数和环境变量 (ARG vs ENV)
ARG和ENV可以帮助我们在构建镜像时,动态的调整所需环境的版本。不同点,ARG只在构建时指定版本,ENV不止在构建时指定版本,还会将环境变量,带入到容器中
- 初始没有加环境变量,版本号是固定的
FROM ubuntu:21.04
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
- ENV的加入使得版本有效的进行动态控制
FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
ENV 设置的变量可以在Image中保持,并在容器中的环境变量里
- ARG
FROM ubuntu:21.04
ARG VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
另外ARG 可以在镜像build的时候动态修改value, 通过 --build-arg
$ docker image build -f .\Dockerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSION=2.0.0 .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ipinfo-arg-2.0.0 latest 0d9c964947e2 6 seconds ago 124MB
$ docker container run -it ipinfo-arg-2.0.0
root@b64285579756:/#
root@b64285579756:/# ipinfo version
2.0.0
root@b64285579756:/#
4.5 CMD容器启动命令
CMD可以用来设置容器启动时默认会执行的命令。
- 容器启动时默认执行的命令
- 如果docker container run启动容器时指定了其它命令,则CMD命令会被忽略
- 如果定义了多个CMD,只有最后一个会被执行。
FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
$ docker image build -t ipinfo .
$ docker container run -it ipinfo
root@8cea7e5e8da8:/#
root@8cea7e5e8da8:/#
root@8cea7e5e8da8:/#
root@8cea7e5e8da8:/# pwd
/
root@8cea7e5e8da8:/#
默认进入到shell是因为在ubuntu的基础镜像里有定义CMD,查看分层可以看到自带 /bin/sh 命令
$docker image history ipinfo
IMAGE CREATED CREATED BY SIZE COMMENT
db75bff5e3ad 24 hours ago RUN /bin/sh -c apt-get update && apt-get… 50MB buildkit.dockerfile.v0
<missing> 24 hours ago ENV VERSION=2.0.1 0B buildkit.dockerfile.v0
<missing> 7 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 7 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 7 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
<missing> 7 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
<missing> 7 days ago /bin/sh -c #(nop) ADD file:d6b6ba642344138dc… 74.1MB
小技巧 当我们启动容器执行命令后,会产生新的容器,造成了容器列表存在的比较多,那么可以增加一个参数
docker container run --rm -it 镜像名 命令
4.6 容器启动命令 ENTRYPOINT
ENTRYPOINT 也可以设置容器启动时要执行的命令,但是和CMD是有区别的。
CMD设置的命令,可以在docker container run 时传入其它命令,覆盖掉dockerfile中CMD的命令,但是ENTRYPOINT所设置的命令是一定会被执行的。ENTRYPOINT和CMD可以联合使用,ENTRYPOINT设置执行的命令,CMD传递参数
FROM ubuntu:21.04
CMD ["echo", "hello docker"]
把上面的Dockerfile build成一个叫 demo-cmd 的镜象
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-cmd latest 5bb63bb9b365 8 days ago 74.1MB
FROM ubuntu:21.04
ENTRYPOINT ["echo", "hello docker"]
build成一个叫 demo-entrypoint 的镜像
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-entrypoint latest b1693a62d67a 8 days ago 74.1MB
CMD的镜像,如果执行创建容器,不指定运行时的命令,则会默认执行CMD所定义的命令,打印出hello docker
$ docker container run -it --rm demo-cmd
hello docker
但是如果我们docker container run的时候指定命令,则该命令会覆盖掉CMD的命令,如:
$ docker container run -it --rm demo-cmd echo "hello world"
hello world
但是ENTRYPOINT的容器里ENTRYPOINT所定义的命令则无法覆盖,一定会执行
$ docker container run -it --rm demo-entrypoint
hello docker
$ docker container run -it --rm demo-entrypoint echo "hello world"
hello docker echo hello world
$
4.7 Shell 格式和 Exec 格式
-
shell 表示通过脚本或命令去执行
如:echo "hello world"
-
exec 表示当成可执行的命令,后边会携带参数,推荐使用该模式
如: ["echo", "hello world"]
使用exec 注意事项,特别是使用到shell变量时,需要用shell去执行,一下是案例:
FROM ubuntu:21.04
ENV NAME=docker
CMD echo "hello $NAME"
假如我们要把上面的CMD改成Exec格式,下面这样改是不行的, 大家可以试试。
FROM ubuntu:21.04
ENV NAME=docker
CMD ["echo", "hello $NAME"]
它会打印出 hello $NAME , 而不是 hello docker ,那么需要怎么写呢? 我们需要以shell脚本的方式去执行
FROM ubuntu:21.04
ENV NAME=docker
CMD ["sh", "-c", "echo hello $NAME"]
4.8 构建python flask镜像
Python 程序
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
Dockerfile
FROM python:3.9.5-slim
ENV FLASK_APP=app.py
WORKDIR /src
COPY app.py /src/app.py
RUN pip install flask
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
运行命令
docker container run -d -p 5003:5000 demo_flask # 5003 映射宿主机端口,5000是容器端口
以上仅是一个低质量的一个dockerfile构建,还存在一定问题,仅作为一个教学。
4.9 Dockerfile最佳实践之利用缓存
- 容易经常发生变更的放到dockerfile末尾,不容易发生改变的放置前方
FROM python:3.9.5-slim
ENV FLASK_APP=app.py
WORKDIR /src
COPY app.py /src/app.py
RUN pip install flask
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
上述场景:当第一次构建完成后,我在本地修改了app.py文件,重新build发现构建速度跟之前一样缓慢
FROM python:3.9.5-slim
ENV FLASK_APP=app.py
WORKDIR /src
RUN pip install flask
EXPOSE 5000
COPY app.py /src/app.py
CMD ["flask", "run", "-h", "0.0.0.0"]
上述场景:当第一次构建完成后,我在本地修改了app.py文件,重新build发现构建速度变快,docker利用了CACHE把之前未发生变化的做了缓存,再次构建时,不再需要重复下载等,节约了时间。
4.10 Dockerfile最佳实践之.dockerignore
使用场景:当在本地文件夹test下构建了一个dockerfile
test本地文件中包含了:
venv文件夹 50M
dockerfile 20KB
app.py 1KB
当通过命令build构建镜像docker image build dockerfile -t demo_image . ,通过这个命令构建它会把本文件夹下所有的文件进行构建,为了解决构建时携带了不必要或不需的文件,增大了镜像的臃肿,使用.dockerignore来解决这个问题,其次可以忽悠一些私密文件不想被传到镜像中,起到一个过滤的效果
在构建docker镜像的时候,需要把所需要的文件由CLI(client)发给Server,这些文件实际上就是build context
. 这个参数就是代表了build context所指向的目录
- 未添加.dockerignore文件,体积大小
-
添加.dockerignore文件
1.需要在构建dockerfile 同级目录下新建.dockerignore
2.在.dockerignore中添加忽悠的文件或文件夹名字
5.1 Dockerfile最佳实践之镜像多阶段构建
适用于 语言输出编译后使用的一些场景,编译时需要安装需要的环境,编译完后,程序运行是不必要的,这种情况可以使用多阶段构建,先编译 后运行,一下案例为:使用gcc环境编译,后在alpine中将第一阶段构建好的gcc文件进行复制,第二阶段就进行运行。
FROM gcc:9.4 AS builder
COPY hello.c /src/hello.c
WORKDIR /src
RUN gcc --static -o hello hello.c
FROM alpine:3.13.5
COPY --from=builder /src/hello /src/hello
ENTRYPOINT [ "/src/hello" ]
CMD []
4.11 Dockerfile最佳实践之尽量使用非root用户
dockerfile 构建非root用户 案例:
- 通过groupadd和useradd创建一个flask的组和用户
- 通过USER指定后面的命令要以flask这个用户的身份运行
FROM python:3.9.5-slim
RUN pip install flask && \
groupadd -r flask && useradd -r -g flask flask && \
mkdir /src && \
chown -R flask:flask /src
USER flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK_APP=app.py
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
ROOT危险:
docker的root权限一直是其遭受诟病的地方,docker的root权限有那么危险么?我们举个例子。
假如我们有一个用户,叫demo,它本身不具有sudo的权限,所以就有很多文件无法进行读写操作,比如/root目录它是无法查看的。
[demo@docker-host ~]$ sudo ls /root
[sudo] password for demo:
demo is not in the sudoers file. This incident will be reported.
[demo@docker-host ~]$
但是这个用户有执行docker的权限,也就是它在docker这个group里。
[demo@docker-host ~]$ groups
demo docker
[demo@docker-host ~]$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest a9d583973f65 2 days ago 1.23MB
[demo@docker-host ~]$
这时,我们就可以通过Docker做很多越权的事情了,比如,我们可以把这个无法查看的/root目录映射到docker container里,你就可以自由进行查看了。
[demo@docker-host vagrant]$ docker run -it -v /root/:/root/tmp busybox sh
/ # cd /root/tmp
~/tmp # ls
anaconda-ks.cfg original-ks.cfg
~/tmp # ls -l
total 16
-rw------- 1 root root 5570 Apr 30 2020 anaconda-ks.cfg
-rw------- 1 root root 5300 Apr 30 2020 original-ks.cfg
~/tmp #
更甚至我们可以给我们自己加sudo权限。我们现在没有sudo权限
[demo@docker-host ~]$ sudo vim /etc/sudoers
[sudo] password for demo:
demo is not in the sudoers file. This incident will be reported.
[demo@docker-host ~]$
但是我可以给自己添加。
[demo@docker-host ~]$ docker run -it -v /etc/sudoers:/root/sudoers busybox sh
/ # echo "demo ALL=(ALL) ALL" >> /root/sudoers
/ # more /root/sudoers | grep demo
demo ALL=(ALL) ALL
然后退出container,bingo,我们有sudo权限了。
[demo@docker-host ~]$ sudo more /etc/sudoers | grep demo
demo ALL=(ALL) ALL
[demo@docker-host ~]$
需要更加进阶就要经常看以下内容:
更多官方dockerfile语法:docs.docker.com/engine/refe… github 官方镜像展示的dockerfile:github.com/docker-libr…
五、Docker存储
默认情况下,在运行中的容器里创建的文件,被保存在一个可写的容器层:
- 如果容器被删除了,则数据也没有了
- 这个可写的容器层是和特定的容器绑定的,也就是这些数据无法方便的和其它容器共享
Docker主要提供了两种方式做数据的持久化
- Data Volume, 由Docker管理,(/var/lib/docker/volumes/ Linux), 持久化数据的最好方式
- Bind Mount,由用户指定存储的数据具体mount在系统什么位置
- 指定了持久化路径,容器与宿主机会实时同步,宿主机
5.0 Data Volume
部分操作需要Linux系统的环境,但是大部分都可以在Windows环境下的Docker进行操作,只有一个操作不行。 环境准备 准备一个Dockerfile 和一个 my-cron的文件,dockerfile可能运行失败,需要翻墙
- 方式一:数据持久化需要在dockerfile中填写 VOLUME "制定要保存的容器目录或文件"
$ ls
Dockerfile my-cron
$ more my-cron
*/1 * * * * date >> /app/test.txt
FROM alpine:latest
RUN apk update
RUN apk --no-cache add curl
ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.1.12/supercronic-linux-amd64 \
SUPERCRONIC=supercronic-linux-amd64 \
SUPERCRONIC_SHA1SUM=048b95b48b708983effb2e5c935a1ef8483d9e3e
RUN curl -fsSLO "$SUPERCRONIC_URL" \
&& echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \
&& chmod +x "$SUPERCRONIC" \
&& mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
&& ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic
COPY my-cron /app/my-cron
WORKDIR /app
VOLUME ["/app"]
# RUN cron job
CMD ["/usr/local/bin/supercronic", "/app/my-cron"]
此时Docker会自动创建一个随机名字的volume,去存储我们在Dockerfile定义的volume
- docker volume ls 查看本地保存的所有volume
- docker volume prune 删除本地所有的volume
- docker volume inspect volume名称 返回有关卷的信息。默认情况下,此命令将所有结果呈现在 JSON 数组中
Mountpoint 表示存储在本地的一个路径
- 方式二:情景:当发生意外,我们需要重新创建容器,如何在创建时将继续使用之前保存好的数据呢?
解决方法:创建容器时,给volume创建一个固定的名称,通过
-v参数我们可以手动的指定需要创建Volume的名字,以及对应于容器内的路径,这个路径是可以任意的,不必需要在Dockerfile里通过VOLUME定义(推荐)
docker image build -t my-cron .
$ docker container run -d -v cron-data:/app my-cron
43c6d0357b0893861092a752c61ab01bdfa62ea766d01d2fcb8b3ecb6c88b3de
$ docker volume ls
DRIVER VOLUME NAME
local cron-data
$ docker volume inspect cron-data
[
{
"CreatedAt": "2021-06-22T23:25:02+02:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/cron-data/_data",
"Name": "cron-data",
"Options": null,
"Scope": "local"
}
]
$ ls /var/lib/docker/volumes/cron-data/_data
my-cron
$ ls /var/lib/docker/volumes/cron-data/_data
my-cron test.txt
5.1 Data Volume 练习 MySQL
使用MySQL官方镜像,tag版本5.7
Dockerfile可以在这里查看 github.com/docker-libr…
准备镜像
$ docker pull mysql:5.7
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 2c9028880e58 5 weeks ago 447MB
创建容器
关于MySQL的镜像使用,可以参考dockerhub hub.docker.com/_/mysql?tab…
关于Dockerfile Volume的定义,可以参考 github.com/docker-libr…
$ docker container run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -v mysql-data:/var/lib/mysql mysql:5.7
02206eb369be08f660bf86b9d5be480e24bb6684c8a938627ebfbcfc0fd9e48e
$ docker volume ls
DRIVER VOLUME NAME
local mysql-data
$ docker volume inspect mysql-data
[
{
"CreatedAt": "2021-06-21T23:55:23+02:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/mysql-data/_data",
"Name": "mysql-data",
"Options": null,
"Scope": "local"
}
]
$
数据库写入数据
进入MySQL的shell,密码是 my-secret-pw
$ docker container exec -it 022 sh
# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.34 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
mysql> create database demo;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| demo |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> exit
Bye
# exit
创建了一个叫 demo的数据库
查看data volume
$ docker volume inspect mysql-data
[
{
"CreatedAt": "2021-06-22T00:01:34+02:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/mysql-data/_data",
"Name": "mysql-data",
"Options": null,
"Scope": "local"
}
]
$ ls /var/lib/docker/volumes/mysql-data/_data
auto.cnf client-cert.pem ib_buffer_pool ibdata1 performance_schema server-cert.pem
ca-key.pem client-key.pem ib_logfile0 ibtmp1 private_key.pem server-key.pem
ca.pem demo ib_logfile1 mysql public_key.pem sys
$
5.2 Bind Mount自定义存储数据路径
- 之前存储路径是由docker指定存储路径
docker container run -d -v cron-data:/app my-cron
-
自定义存储路径liunx系统
$(pwd)表示在当前路径
docker container run -d -v $(pwd):/app my-cron
Bind Mount 使用场景如:实时交互性强,我需要在容器中编译好文件后保存到宿主机内
5.3 多个机器之间的容器共享数据
官方参考链接 docs.docker.com/storage/vol…
Docker的volume支持多种driver。默认创建的volume driver都是local,支持云存储等,以下是其中的一种
$ docker volume inspect vscode
[
{
"CreatedAt": "2021-06-23T21:33:57Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/vscode/_data",
"Name": "vscode",
"Options": null,
"Scope": "local"
}
]
这一节我们看看一个叫sshfs的driver,如何让docker使用不在同一台机器上的文件系统做volume
环境准备
准备三台Linux机器,之间可以通过SSH相互通信。
| hostname | ip | ssh username | ssh password |
|---|---|---|---|
| docker-host1 | 192.168.200.10 | vagrant | vagrant |
| docker-host2 | 192.168.200.11 | vagrant | vagrant |
| docker-host3 | 192.168.200.12 | vagrant | vagrant |
安装plugin (driver之一)
在其中两台机器上安装一个plugin vieux/sshfs
[vagrant@docker-host1 ~]$ docker plugin install --grant-all-permissions vieux/sshfs
latest: Pulling from vieux/sshfs
Digest: sha256:1d3c3e42c12138da5ef7873b97f7f32cf99fb6edde75fa4f0bcf9ed277855811
52d435ada6a4: Complete
Installed plugin vieux/sshfs
[vagrant@docker-host2 ~]$ docker plugin install --grant-all-permissions vieux/sshfs
latest: Pulling from vieux/sshfs
Digest: sha256:1d3c3e42c12138da5ef7873b97f7f32cf99fb6edde75fa4f0bcf9ed277855811
52d435ada6a4: Complete
Installed plugin vieux/sshfs
创建volume
-o sshcmd:指定用户名:IP地址:目录(远程机器的地址) sshvolume:volume名字
[vagrant@docker-host1 ~]$ docker volume create --driver vieux/sshfs \
-o sshcmd=vagrant@192.168.200.12:/home/vagrant \
-o password=vagrant \
sshvolume
查看
[vagrant@docker-host1 ~]$ docker volume ls
DRIVER VOLUME NAME
vieux/sshfs:latest sshvolume
[vagrant@docker-host1 ~]$ docker volume inspect sshvolume
[
{
"CreatedAt": "0001-01-01T00:00:00Z",
"Driver": "vieux/sshfs:latest",
"Labels": {},
"Mountpoint": "/mnt/volumes/f59e848643f73d73a21b881486d55b33",
"Name": "sshvolume",
"Options": {
"password": "vagrant",
"sshcmd": "vagrant@192.168.200.12:/home/vagrant"
},
"Scope": "local"
}
]
创建容器挂载Volume
创建容器,挂载sshvolume到/app目录,然后进入容器的shell,在/app目录创建一个test.txt文件
[vagrant@docker-host1 ~]$ docker run -it -v sshvolume:/app busybox sh
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
b71f96345d44: Pull complete
Digest: sha256:930490f97e5b921535c153e0e7110d251134cc4b72bbb8133c6a5065cc68580d
Status: Downloaded newer image for busybox:latest
/ #
/ # ls
app bin dev etc home proc root sys tmp usr var
/ # cd /app
/app # ls
/app # echo "this is ssh volume"> test.txt
/app # ls
test.txt
/app # more test.txt
this is ssh volume
/app #
/app #
这个文件我们可以在docker-host3上看到
[vagrant@docker-host3 ~]$ pwd
/home/vagrant
[vagrant@docker-host3 ~]$ ls
test.txt
[vagrant@docker-host3 ~]$ more test.txt
this is ssh volume
六、Docker网络
面试常问的一个题目, 当你在浏览器中输入一个网址(比如www.baidu.com)并敲回车,这个过程后面都发生了什么
-
本地电脑寻找DNS缓存,没有发送DNS请求进行解析,并将域名转换成IP地址
-
通过路由转换,将本地IP转换为公有IP,使用公有IP与服务器进行连接和通信,此技术称之为NAT(网络技术转换)
-
计算机发送一个 TCP SYN 消息,Web 服务器使用 TCP SYN-ACK 进行响应,计算机发送一个 TCP ACK,俗称 三次握手
-
网页浏览器与网页服务器对话
6.1 网络常用命令
P地址的查看
Windows
ipconfig
Linux
ifconfig
或者
ip addr
网络连通性测试
ping命令
PS C:\Users > ping 192.168.178.1
Pinging 192.168.178.1 with 32 bytes of data:
Reply from 192.168.178.1: bytes=32 time=2ms TTL=64
Reply from 192.168.178.1: bytes=32 time=3ms TTL=64
Reply from 192.168.178.1: bytes=32 time=3ms TTL=64
Reply from 192.168.178.1: bytes=32 time=3ms TTL=64
Ping statistics for 192.168.178.1:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 2ms, Maximum = 3ms, Average = 2ms
telnet命令
测试端口的连通性
➜ ~ telnet www.baidu.com 80
Trying 104.193.88.123...
Connected to www.wshifen.com.
Escape character is '^]'.
HTTP/1.1 400 Bad Request
Connection closed by foreign host.
➜ ~
traceroute
路径探测跟踪
Linux下使用 tracepath
➜ ~ tracepath www.baidu.com
1?: [LOCALHOST] pmtu 1500
1: DESKTOP-FQ0EO8J 0.430ms
1: DESKTOP-FQ0EO8J 0.188ms
2: 192.168.178.1 3.371ms
3: no reply
4: gv-rc0052-cr102-et91-251.core.as33915.net 13.970ms
5: asd-tr0021-cr101-be156-10.core.as9143.net 19.190ms
6: nl-ams04a-ri3-ae51-0.core.as9143.net 213.589ms
7: 63.218.65.33 16.887ms
8: HundredGE0-6-0-0.br04.sjo01.pccwbtn.net 176.099ms asymm 10
9: HundredGE0-6-0-0.br04.sjo01.pccwbtn.net 173.399ms asymm 10
10: 63-219-23-98.static.pccwglobal.net 177.337ms asymm 11
11: 104.193.88.13 178.197ms asymm 12
12: no reply
13: no reply
14: no reply
15: no reply
16: no reply
17: no reply
18: no reply
19: no reply
20: no reply
21: no reply
22: no reply
23: no reply
24: no reply
25: no reply
26: no reply
27: no reply
28: no reply
29: no reply
30: no reply
Too many hops: pmtu 1500
Resume: pmtu 1500
➜ ~
Windows下使用 TRACERT.EXE
PS C:\Users> TRACERT.EXE www.baidu.com
Tracing route to www.wshifen.com [104.193.88.123]
over a maximum of 30 hops:
1 4 ms 3 ms 3 ms 192.168.178.1
2 * * * Request timed out.
3 21 ms 18 ms 19 ms gv-rc0052-cr102-et91-251.core.as33915.net [213.51.197.37]
4 14 ms 13 ms 12 ms asd-tr0021-cr101-be156-10.core.as9143.net [213.51.158.2]
5 23 ms 19 ms 14 ms nl-ams04a-ri3-ae51-0.core.as9143.net [213.51.64.194]
6 15 ms 14 ms 13 ms 63.218.65.33
7 172 ms 169 ms 167 ms HundredGE0-6-0-0.br04.sjo01.pccwbtn.net [63.223.60.58]
8 167 ms 168 ms 168 ms HundredGE0-6-0-0.br04.sjo01.pccwbtn.net [63.223.60.58]
9 168 ms 173 ms 167 ms 63-219-23-98.static.pccwglobal.net [63.219.23.98]
10 172 ms 170 ms 171 ms
curl命令
请求web服务的
www.ruanyifeng.com/blog/2019/0…
6.2 容器网络连接
Docker Bridge 网络(桥接)
问题:一台宿主机可以ping通容器中的IP地址,且容器之间可以相互ping通
- 两台电脑通信
- 多台电脑通信,引入交换机
- docker提供了网络连接服务,当在宿主机输入 ip addr时 会出现Docker的网络信息
- Docker通信模式
- 最下部分eth0表示与外部进行通信
- docker0:创建容器默认会连接到docker0上
- 容器eth0和veth1:容器与docker0连接需要一根线,这根线就类似于 电脑网线端口一端插入电脑,另一端插入路由器或交换机
docker network ls # 查看
docker network inspect bridge 网络ID
两个容器都连接到了一个叫 docker0 的Linux bridge上
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
1847e179a316 bridge bridge local
a647a4ad0b4f host host local
fbd81b56c009 none null local
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "1847e179a316ee5219c951c2c21cf2c787d431d1ffb3ef621b8f0d1edd197b24",
"Created": "2021-07-01T15:28:09.265408946Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"03494b034694982fa085cc4052b6c7b8b9c046f9d5f85f30e3a9e716fad20741": {
"Name": "box1",
"EndpointID": "072160448becebb7c9c333dce9bbdf7601a92b1d3e7a5820b8b35976cf4fd6ff",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"4f3303c84e5391ea37db664fd08683b01decdadae636aaa1bfd7bb9669cbd8de": {
"Name": "box2",
"EndpointID": "4cf0f635d4273066acd3075ec775e6fa405034f94b88c1bcacdaae847612f2c5",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
- 扩展:
brctl命令:查询网桥信息,一个网桥对应可以有多个接口信息
brctl 使用前需要安装, 对于CentOS, 可以通过 sudo yum install -y bridge-utils 安装. 对于Ubuntu, 可以通过 sudo apt-get install -y bridge-utils
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242759468cf no veth8c9bb82
vethd8f9afb
容器对外通信
问题二:容器内部能PING通外部服务器地址原因(宿主机能够访问外网) 查看路由
$ ip route
default via 10.0.2.2 dev eth0 proto dhcp metric 100 # 默认路由(访问非容器内会走该路由经过eth0出去)
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 # docker0连接的容器
192.168.200.0/24 dev eth1 proto kernel scope link src 192.168.200.10 metric 101
容器ping baidu.com案例流程:
1)容器发送外部请求,docker0进行判断,转发外部eth0上
2)请求出去之前,会经过iptables中网络地址转换(NAT),将容器地址转换为外部默认地址eth0
iptable 转发规则
$ sudo iptables --list -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- anywhere !loopback/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 anywhere
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- anywhere anywhere
3)正式发送请求
6.3 创建和使用 bridge
用途:需要让容器指定bridge 时,默认会连接docker0
自定义bridge(日常使用建议自定义网络)
- -d 表示使用指定driver
docker network create -d bridge my_bridge自定义名称
创建容器时指定bridge
- 参数--network bridge自定义名称,通过该参数去指定
docker container run -d --rm --name client busybox --network mybridge /bin/sh -c "while true; do sleep 3600; done"
容器同时连接两个网络
docker network ls 查询需要额外连接的bridge名称
连接命令:
docker network connect bridge名称 容器名称
关闭命令:
docker network disconnect bridge名称 容器名称
具备DNS功能
容器之间可以通过自定义bridge ping 对方容器名称 是可以的,但容器默认使用的docker0 是不支持的
自定义网关和子网
当自定义docker bridge时,docker会依次创建好新的,如果想自定义,不让docker来进行创建,可通过以下方式
- 默认创建 docker bridge 信息
-
创建容器时自定义网关和子网信息
- --gateway 参数指定网关
- --subnet 网段
案例:
docker network create -d bridge --gateway 172.200.0.1 --subnet 172.200.0.0/16 demo
6.4 容器端口转发
场景:
- 物理机A内docker容器能访问物理机B IP地址
- 物理机B访问物理机A内的docker容器IP地址,访问不通
- 解决方案:容器和物理机进行端口转换,容器发出走NAT 容器收消息使用端口转发
- -p 参数表示端口转发, 左侧端口表示物理机端口:右侧端口表示容器端口
案例:Nginx 转发
docker container run -d --name web -p 8080:80 nginx
快速获取容器IP地址命令
docker container inspect --format '{{ .NetworkSettings.IPAddress }}' 容器ID或名字
查看iptables的端口转发规则
[vagrant@docker-host1 ~]$ sudo iptables -t nat -nvxL
Chain PREROUTING (policy ACCEPT 10 packets, 1961 bytes)
pkts bytes target prot opt in out source destination
1 52 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 9 packets, 1901 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 2 packets, 120 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 4 packets, 232 bytes)
pkts bytes target prot opt in out source destination
3 202 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
1 52 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080
查看官方镜像EXPOSE 端口号
- EXPOSE 解释说明 镜像的端口映射,当查看镜像详细信息时会看到端口信息
6.5 容器网络三种模型区别
- bridge网络: docker默认网络,
- host: 连接到host网络的容器共享Docker host的网络栈,容器的网络配置与host完全一样
- none网络就是什么都没有的网络。挂在这个网络下的容器除了1o,没有其他任何网卡
七、Docker Compose
假设要启动N个docker容器,需要手动的去敲命令,非常麻烦,Docker Compose可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具,通过固定语法编写yaml文件
7.1 Docker Compose 安装
最新版本号可以在这里查询 github.com/docker/comp…
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c
熟悉python的朋友,可以使用pip去安装docker-Compose
$ pip install docker-compose
7.2 Docker Compose 语法
docker compose文件的语法说明 docs.docker.com/compose/com…
7.3 基本语法结构
- 编写docker-compose时,建议使用VScode compose插件进行编写,能够自动识别语法错误
- docker-compose 命令执行,未在compose中写入镜像构建和拉取时,需要提前先把容器运行起来,
version: "3.8" # docker compose 语法版本,冒号后必须有空格
services: # 定义的容器,可以在内部定义多个容器,必填
servicename: # 服务名字(必填)类似于 --name 这个名字也是内部 bridge网络可以使用的 DNS name
image: # 使用镜像的名字(必填),来创建容器
command: # 可选,如果设置,则会覆盖默认镜像里的 CMD命令
environment: # 可选,相当于 docker run里的 --env
volumes: # 可选,相当于docker run里的 -v
networks: # 可选,相当于 docker run里的 --network,指定连接的网络
ports: # 可选,相当于 docker run里的 -p
servicename2:
...
volumes: # 可选,相当于 docker volume create 会被上边所用的
networks: # 可选,相当于 docker network create 会被上边所用的
7.4 Python Flask + Redis 案例
以下未使用compose
程序准备
准备一个Python文件,名字为 app.py 内容如下:
from flask import Flask
from redis import Redis
import os
import socket
app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
准备一个Dockerfile
FROM python:3.9.5-slim
RUN pip install flask redis && \
groupadd -r flask && useradd -r -g flask flask && \
mkdir /src && \
chown -R flask:flask /src
USER flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK_APP=app.py REDIS_HOST=redis
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
镜像准备
构建flask镜像,准备一个redis镜像。
$ docker image pull redis
$ docker image build -t flask-demo .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-demo latest 4778411a24c5 About a minute ago 126MB
python 3.9.5-slim c71955050276 8 days ago 115MB
redis latest 08502081bff6 2 weeks ago 105MB
创建一个docker bridge
$ docker network create -d bridge demo-network
8005f4348c44ffe3cdcbbda165beea2b0cb520179d3745b24e8f9e05a3e6456d
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
2a464c0b8ec7 bridge bridge local
8005f4348c44 demo-network bridge local
80b63f711a37 host host local
fae746a75be1 none null local
$
创建redis container
创建一个叫 redis-server 的container,连到 demo-network上
$ docker container run -d --name redis-server --network demo-network redis
002800c265020310231d689e6fd35bc084a0fa015e8b0a3174aa2c5e29824c0e
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
002800c26502 redis "docker-entrypoint.s…" 4 seconds ago Up 3 seconds 6379/tcp redis-server
$
创建flask container
$ docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 5000:5000 flask-demo
打开浏览器访问 http://127.0.0.1:5000
应该能看到类似下面的内容,每次刷新页面,计数加1
Hello Container World! I have been seen 36 times and my hostname is 925ecb8d111a.
总结
如果把上面的步骤合并到一起,成为一个部署脚本
# prepare image
docker image pull redis
docker image build -t flask-demo .
# create network
docker network create -d bridge demo-network
# create container
docker container run -d --name redis-server --network demo-network redis
docker container run -d --network demo-network --name flask-demo --env REDIS_HOST=redis-server -p 8080:5000 flask-demo
使用compose docker-compose.yml 文件如下
version: "3.8"
services:
flask-demo:
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- demo-network
ports:
- 8080:5000
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
docker-compose 语法版本
向后兼容,新版docker支持老版本compose语法,反之最新compose语法不知道老版docker
7.5 compose 命令行使用,要所在compose文件目录下执行
docker-compose up 启动
docker-compose up -d 后台启动
docker-compose ps 查看后台services 运行情况
docker-compose stop 停止compose运行的容器
docker-compose rm 删除compose容器
- 删除docker-compose 网络
docker network rm 名称
删除未使用的容器
- 命名问题,通过docker-compose命名 网络和容器名称都有前缀
前缀默认为文件夹的名称
可以自定义进行修改
docker-compose -p 自定义名称 up -d
缺点,不方便,每次查看或停止删除,都需要加上docker-compose -p 自定义名称 rm\stop\ps
- 在docker-compose.yml文件中添加,container_name,但会有影响
version: "3.8"
services:
flask-demo:
container_name: my-flask-demo
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- demo-network
ports:
- 8080:5000
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
7.6 docker-compose 镜像构建和拉取
- 容器未运行状态启动
- 通过docker-compos 进行build 构建 1.需要将构建的dockerfile和文件存放到与dockercompose同级目录中
2.在docker-compose中添加
context:指定文件夹
dockerfile:指定dockerfile文件名
version: "3.8"
services:
flask-demo:
build:
context: ./flask
dockerfile: dockerfile
container_name: my-flask-demo
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- demo-network
ports:
- 8080:5000
redis-server:
image: redis:latest
networks:
- demo-network
networks:
demo-network:
- 本地构建build,拉取需要pull
docker-compose pull
7.7 docker-compose服务更新
- 场景一:当构建镜像文件发生了内容变化,需要进行更新
docker-compose up -d --build # 不加build compose不会进行更新操作 (常用)
- 场景二:新增一个service
docker-compose up -d
- 场景三:删除一个service
docker-compose up -d --remove-orphans (常用)
- 场景四:volume等配置更改
docker-compose up -d restart (常用)
7.7 docker-compose 网络
- docker-compose 不指定网络,会默认创建一个 桥接 网络
两个service能相互ping IP、镜像名称、service 名称、外网,这是因为docker container 会写入docker DNS服务中,同时docker-compose会将service 名称写入DNS服务
- docker-compose 指定网络 单机情况下使用compose,网络默认是bridge
通过配置ipam,指定分配的子网
7.8 docker-compose 水平扩展 scale
环境清理
删除所有容器和镜像
$ docker container rm -f $(docker container ps -aq)
$ docker system prune -a -f
启动
目录结构
- docker-compose.yml
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
redis-server:
image: redis:latest
client:
image: xiaopeng163/net-box:latest
command: sh -c "while true; do sleep 3600; done;"
- Dockerfile
FROM python:3.9.5-slim
RUN pip install flask redis && \
groupadd -r flask && useradd -r -g flask flask && \
mkdir /src && \
chown -R flask:flask /src
USER flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK=app.py REDIS_HOST=redis
EXPOSE 5000
- app.py
from flask import Flask
from redis import Redis
import os
import socket
app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
@app.route('/')
def hello():
redis.incr('hits')
- 启动
$ docker-compose pull
$ docker-compose build
$ docker-compose up -d
Creating network "compose-scale-example_default" with the default driver
Creating compose-scale-example_flask_1 ... done
Creating compose-scale-example_client_1 ... done
Creating compose-scale-example_redis-server_1 ... done
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------
compose-scale-example_client_1 sh -c while true; do sleep ... Up
compose-scale-example_flask_1 flask run -h 0.0.0.0 Up 5000/tcp
compose-scale-example_redis-server_1 docker-entrypoint.sh redis ... Up 6379/tcp
水平扩展 scale
--scale service名称=扩展的数量 可以理解为 将一个容器复制多份,假设扩展数量为3,后又数量为1,compose会自动停止和删除之前多余的扩展
$ docker-compose up -d --scale flask=3
compose-scale-example_client_1 is up-to-date
compose-scale-example_redis-server_1 is up-to-date
Creating compose-scale-example_flask_2 ... done
Creating compose-scale-example_flask_3 ... done
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------
compose-scale-example_client_1 sh -c while true; do sleep ... Up
compose-scale-example_flask_1 flask run -h 0.0.0.0 Up 5000/tcp
compose-scale-example_flask_2 flask run -h 0.0.0.0 Up 5000/tcp
compose-scale-example_flask_3 flask run -h 0.0.0.0 Up 5000/tcp
compose-scale-example_redis-server_1 docker-entrypoint.sh redis ... Up 6379/tcp
(base) [root@localhost compose-scale-example-1]# docker-compose up -d --scale flask=1
compose-scale-example-1_client_1 is up-to-date
compose-scale-example-1_redis-server_1 is up-to-date
Stopping and removing compose-scale-example-1_flask_2 ... done
Stopping and removing compose-scale-example-1_flask_3 ... done
(base) [root@localhost compose-scale-example-1]# docker-compose ps
Name Command State Ports
------------------------------------------------------------------------------------------
compose-scale-example-1_client_1 sh -c while true; do sleep ... Up
compose-scale-example-1_flask_1 flask run -h 0.0.0.0 Up 5000/tcp
compose-scale-example-1_redis-server_1 docker-entrypoint.sh redis ... Up 6379/tcp
当service数量进行扩展,访问service时,docker对service进行了负载均衡的操作
添加nginx,典型服务部署方式
- 文件架构
- 新增了 nginx 配置
(base) [root@localhost compose-scale-example-2]# cat nginx/nginx.conf
server {
listen 80 default_server;
location / {
proxy_pass http://flask:5000;
}
- compose文件内容 depends_on表示只有先启动了此service完成后,才会启动本service
:ro表示容器内不能修改
网络 backend 用于连接redis + flask,frontend用于flask + nginx
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
networks:
- backend
- frontend
redis-server:
image: redis:latest
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
- flask
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
networks:
backend:
frontend:
- 当水平扩展至3个时,nginx会进行负载均衡转发
需要重启nginx加载
docker-compose restart nginx
7.9 docker-compose环境变量
版本一 场景:通过NGINX+FLASK+REDIS 构建建了服务,但是redis没有设置密码,安全性较低,因此,做了以下变动
- compose 设置了密码环境变量
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=abc123
networks:
- backend
- frontend
redis-server:
image: redis:latest
command: redis-server --requirepass abc123
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
- flask
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
networks:
backend:
frontend:
- app.py 通过读取环境变量进行获取
from flask import Flask
from redis import StrictRedis
import os
import socket
app = Flask(__name__)
redis = StrictRedis(host=os.environ.get('REDIS_HOST', '127.0.0.1'),
port=6379, password=os.environ.get('REDIS_PASS'))
@app.route('/')
def hello():
redis.incr('hits')
return f"Hello Container World! I have been seen {redis.get('hits').decode('utf-8')} times and my hostname is {socket.gethostname()}.\n"
版本二 场景:通过NGINX+FLASK+REDIS 构建建了服务,但是redis设置密码在docker-compose文件中,安全性较低,因此,做了以下变动
- compose 设置了密码环境变量,从外部读取
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
networks:
- backend
- frontend
redis-server:
image: redis:latest
command: redis-server --requirepass ${REDIS_PASSWORD}
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
- flask
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
networks:
backend:
frontend:
- Docker提供了环境变量传递方式之一 创建.env文件 在docker-compose同级目录创建
- 防止.env文件被上传或git提交,创建.gitignore、.dockerignore
# cat .gitignore
.env
# cat .dockerignore
.DS_Store
.idea
.git
.gitignore
.env
.dockerignore
Dockerfile
docker-compose.yaml
nginx.conf
var
docker-compose config 验证配置文件
- 如果.env环境未添加会出现告警
- 环境变量发生改变,需要重新up 一下加载配置
- .env 改为自定义名称,docker 指定加载环境命令
docker-compose --env-file 自定义环境名称
7.10 docker-compose服务依赖和检查
场景:通过在compose中定义了 depends_on 保证了启动的先后顺序,却无法保证服务起来后是否能正常工作,此时需要健康检查
dockerfile 健康检查案例
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:5000/ || exit 1 上面Dockerfili里的HEALTHCHECK 就是定义了一个健康检查。 会每隔30秒检查一次,如果失败就会退出,退出代码是1
FROM python:3.9.5-slim
RUN pip install flask redis && \
apt-get update && \
apt-get install -y curl && \
groupadd -r flask && useradd -r -g flask flask && \
mkdir /src && \
chown -R flask:flask /src
USER flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK_APP=app.py REDIS_HOST=redis
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:5000/ || exit 1
CMD ["flask", "run", "-h", "0.0.0.0"]
查看健康检查信息
-
当容器指定了健康检查时,除了正常状态外,它还具有健康状态。* 此状态最初为
starting -
每当健康检查通过时,它就会变成
healthy(无论它以前处于什么状态) -
在连续失败一定次数后,就变成了
unhealthy
健康检查将首先在容器启动后运行间隔秒,然后在每次之前的检查完成后再次运行间隔秒。
如果单次运行检查花费的时间超过timeout秒,则认为检查失败。
它需要重试健康检查的连续失败才能考虑容器unhealthy。
- 查询检查日志
docker container inspect 容器ID
docker-composes 检查
start period为需要时间引导的容器提供初始化时间。在此期间探测失败将不计入最大重试次数。但是,如果在启动期间健康检查成功,则认为容器已启动,所有连续失败将计入最大重试次数。3.4版本以上才能使用 interval:多长时间执行一次
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
案例: 值得注意的是,nginx之前是只要flask启动成功后,就去启动,并未检查healthcheck状态为healthy时启动,因此在nginx下加入了以下代码,意思是只有flask状态为healthy时才启动
depends_on:
flask:
condition: service_healthy
version: "3.8"
services:
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask-demo:latest
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
depends_on:
redis-server:
condition: service_healthy
networks:
- backend
- frontend
redis-server:
image: redis:latest
command: redis-server --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 3s
retries: 30
networks:
- backend
nginx:
image: nginx:stable-alpine
ports:
- 8000:80
depends_on:
flask:
condition: service_healthy
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./var/log/nginx:/var/log/nginx
networks:
- frontend
networks:
backend:
frontend:
docker compose 投票 app 练习
源码地址: github.com/dockersampl…
八、Docker Swarm集群编排
8.1 docker swarm 介绍
为什么不建议在生产环境中使用docker-Compose
- 多机器如何管理?
- 如果跨机器做scale横向扩展?
- 容器失败退出时如何新建容器确保服务正常运行?
- 如何确保零宕机时间?
- 如何管理密码,Key等敏感数据?
- 其它
容器编排 swarm
Swarm的基本架构
8.2 Swarm 单节点快速上手
初始化
docker info 这个命令可以查看我们的docker engine有没有激活swarm模式, 默认是没有的,我们会看到
Swarm: inactive # 未激活
Swarm: active # 激活
激活swarm,有两个方法:
- 初始化一个swarm集群,自己成为manager
- 加入一个已经存在的swarm集群
PS C:\Users\Peng Xiao\code-demo> docker swarm init # 初始化激活swarm
Swarm initialized: current node (vjtstrkxntsacyjtvl18hcbe4) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-33ci17l1n34fh6v4r1qq8qmocjo347saeuer2xrxflrn25jgjx-7vphgu8a0gsa4anof6ffrgwqb 192.168.65.3:2377 # 其他节点加入时复制此命令
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
PS C:\Users\Peng Xiao\code-demo> docker node ls # 查看节点信息
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
vjtstrkxntsacyjtvl18hcbe4 * docker-desktop Ready Active Leader 20.10.7
PS C:\Users\Peng Xiao\code-demo>
docker swarm init 背后发生了什么
主要是PKI和安全相关的自动化
- 创建swarm集群的根证书
- manager节点的证书
- 其它节点加入集群需要的tokens
创建Raft数据库用于存储证书,配置,密码等数据
RAFT相关资料
单节点实战练习
- 初始化和查看节点信息
PS C:\Users\Wxc> docker swarm init
Swarm initialized: current node (li2q4bn8ttj3rkrsh9hh7si05) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-63kt7e6zns9a857r04zkhvsbh3fikby2t4hujl4zyjuj86do7j-1a67wtq388bolssjome0gobts 192.168.65.3:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
PS C:\Users\Wxc> docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
li2q4bn8ttj3rkrsh9hh7si05 * docker-desktop Ready Active Leader 20.10.11
- 创建service 类似于创建container,swram会调度创建新的service存在于哪个节点上 Usage: docker service create [OPTIONS] IMAGE [COMMAND] [ARG...]
PS C:\Users\Wxc> docker service create nginx:latest
j53c9i58ze54sufrpmnox0jmq # service ID
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
- 查看service 信息和详细信息
PS C:\Users\Wxc> docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
j53c9i58ze54 busy_goodall replicated 1/1 nginx:latest
PS C:\Users\Wxc> docker service ps j53c9i58ze54
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
r9rmm4gpix4t busy_goodall.1 nginx:latest docker-desktop Running Running about a minute ago
横向扩展
- docker service update SERVICE_ID --replicas 复制数量
PS C:\Users\Wxc> docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
00ffee5a9d6e nginx:latest "/docker-entrypoint.…" 7 minutes ago Up 7 minutes 80/tcp busy_goodall.1.r9rmm4gpix4tpfakbxkgpd743
PS C:\Users\Wxc> docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
j53c9i58ze54 busy_goodall replicated 1/1 nginx:latest
PS C:\Users\Wxc> docker service ps j53
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
r9rmm4gpix4t busy_goodall.1 nginx:latest docker-desktop Running Running 8 minutes ago
**********************************以下进行扩展**************************************
PS C:\Users\Wxc> docker service update j53 --replicas 3
j53
overall progress: 3 out of 3 tasks
1/3: running [==================================================>]
2/3: running [==================================================>]
3/3: running [==================================================>]
verify: Service converged
PS C:\Users\Wxc> docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
j53c9i58ze54 busy_goodall replicated 3/3 nginx:latest
PS C:\Users\Wxc> docker service ps j53
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
r9rmm4gpix4t busy_goodall.1 nginx:latest docker-desktop Running Running 9 minutes ago
s8hyqbgxprio busy_goodall.2 nginx:latest docker-desktop Running Running 16 seconds ago
8qkgu1j3stb0 busy_goodall.3 nginx:latest docker-desktop Running Running 16 seconds ago
PS C:\Users\Wxc> docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6ee54fa17354 nginx:latest "/docker-entrypoint.…" 23 seconds ago Up 22 seconds 80/tcp busy_goodall.2.s8hyqbgxprio9xef2a6fqt1jp
d6eef01a92a3 nginx:latest "/docker-entrypoint.…" 23 seconds ago Up 22 seconds 80/tcp busy_goodall.3.8qkgu1j3stb0hq9rslldvc5gb
00ffee5a9d6e nginx:latest "/docker-entrypoint.…" 9 minutes ago Up 9 minutes 80/tcp busy_goodall.1.r9rmm4gpix4tpfakbxkgpd743
- 上述操作通过node查看得知都在单节点上
- 当其中一个service宕机,swarm会自动重新启动一个新的service替代
service 删除
- docker service rm SERVICE_ID
8.3 Swarm 三节点集群搭建
多节点的环境涉及到机器之间的通信需求,所以防火墙和网络安全策略组是大家一定要考虑的问题,特别是在云上使用云主机的情况,下面这些端口记得打开 防火墙 以及 设置安全策略组
- TCP port
2376 - TCP port
2377 - TCP and UDP port
7946 - UDP port
4789
为了简化,以上所有端口都允许节点之间自由访问就行。
1.创建3台虚拟机或物理机
2.在一台主机上进行初始化和选择网络
- --advertise-addr指定网络地址 主节点
# docker swarm init
Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on interface em1 (2400:dd01:1032:116:72f0:83e0:9d44:81ee and 2001:cc0:2016:f029:6e82:dea6:e02:54cf) - specify one with --advertise-addr
# docker swarm init --advertise-addr 2001:cc0:2016:f029:6e82:dea6:e02:54cf
Swarm initialized: current node (wsenrrvb1v59q7da20o5w1f96) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-4aau1gg62vkvrhvukw8vtzvzn6ldrm7066dqpb0ap8j1f00tsm-05u23fy89oesc7vgo3kk7pm6s [2001:cc0:2016:f029:6e82:dea6:e02:54cf]: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
wsenrrvb1v59q7da20o5w1f96 * localhost.localdomain Ready Active Leader 20.10.11
worker节点-1
# docker swarm join --token SWMTKN-1-4aau1gg62vkvrhvukw8vtzvzn6ldrm7066dqpb0ap8j1f00tsm-05u23fy89oesc7vgo3kk7pm6s [2001:cc0:2016:f029:6e82:dea6:e02:54cf]:2377
worker节点-2
# docker swarm join --token SWMTKN-1-4aau1gg62vkvrhvukw8vtzvzn6ldrm7066dqpb0ap8j1f00tsm-05u23fy89oesc7vgo3kk7pm6s [2001:cc0:2016:f029:6e82:dea6:e02:54cf]:2377
主节点查看

docker service 命令
| 命令 | 描述 |
|---|---|
| docker service create | 创建新服务 |
| docker service inspect | 显示一项或多项服务的详细信息 |
| docker service logs | 获取服务或任务的日志 |
| docker service ls | 列出服务 |
| docker service ps | 列出一项或多项服务的任务 |
| docker service rm | 删除一项或多项服务 |
| docker service rollback | 恢复对服务配置的更改 |
| docker service scale | 扩展一个或多个复制服务 |
| docker service upodate | 更新服务 |
官方文档:docs.docker.com/engine/refe…
Swarm 集群service
- 命令必须在主节点执行,必须是manager机器 执行命令
创建nginx,并复制3份
swarm自动分配到不同机器上,需要注意的是,容器出现宕机情况,会自动启动分配的某个节点上
- 命令实践
# 扩展一个或多个复制服务
docker service scale SERVICE_ID=数量
# 查看logs
docker service logs SERVICE_ID或NAME
docker service logs -f SERVICE_ID或NAME # 一直监听LOG
8.4 swarm overlay 网络
overlay网络
对于理解swarm的网络来讲,个人认为最重要的两个点:
-
第一是外部如何访问部署运行在swarm集群内的服务,可以称之为
入方向流量,在swarm里我们通过ingress来解决 -
第二是部署在swarm集群里的服务,如何对外进行访问,这部分又分为两块:
- 第一,
东西向流量,也就是不同swarm节点上的容器之间如何通信,swarm通过overlay网络来解决; - 第二,
南北向流量,也就是swarm集群里的容器如何对外访问,比如互联网,这个是Linux bridge + iptables NAT来解决的
- 第一,
创建overlay网络
docker network create -d overlay 自定义名称
创建service指定overlay网络
docker service create --network 网络名称 --name service名称 --replicas 指定复制数量 镜像名称
- 查看容器,容器内存在两个网络,一个是bridge 一个是overlay网络
- 容器对外通信使用bridage网络
- 节点与节点通信,需要走overlay网络
8.5 Swarm 的 ingress网络
单个机器创建容器可以通过端口映射方式放外部访问到容器内
docker swarm的ingress网络又叫 Ingress Routing Mesh
主要是为了实现把service的服务端口对外发布出去,让其能够被外部网络访问到。
ingress routing mesh是docker swarm网络里最复杂的一部分内容,包括多方面的内容:
- iptables的 Destination NAT流量转发
- Linux bridge, network namespace
- 使用IPVS技术做负载均衡
- 包括容器间的通信(overlay)和入方向流量的端口转发
service创建
创建一个service,指定网络是overlay的mynet, 通过-p把端口映射出来
我们使用的镜像 containous/whoami 是一个简单的web服务,能返回服务器的hostname,和基本的网络信息,比如IP地址
vagrant@swarm-manager:~$ docker service create --name web --network mynet -p 8080:80 --replicas 2 containous/whoami
a9cn3p0ovg5jcz30rzz89lyfz
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service converged
vagrant@swarm-manager:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
a9cn3p0ovg5j web replicated 2/2 containous/whoami:latest *:8080->80/tcp
vagrant@swarm-manager:~$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
udlzvsraha1x web.1 containous/whoami:latest swarm-worker1 Running Running 16 seconds ago
mms2c65e5ygt web.2 containous/whoami:latest swarm-manager Running Running 16 seconds ago
vagrant@swarm-manager:~$
service的访问
8080这个端口到底映射到哪里了?尝试三个swarm节点的IP加端口8080
可以看到三个节点IP都可以访问,并且回应的容器是不同的(hostname),也就是有负载均衡的效果
vagrant@swarm-manager:~$ curl 192.168.200.10:8080
Hostname: fdf7c1354507
IP: 127.0.0.1
IP: 10.0.0.7
IP: 172.18.0.3
IP: 10.0.1.14
RemoteAddr: 10.0.0.2:36828
GET / HTTP/1.1
Host: 192.168.200.10:8080
User-Agent: curl/7.68.0
Accept: */*
vagrant@swarm-manager:~$ curl 192.168.200.11:8080
Hostname: fdf7c1354507
IP: 127.0.0.1
IP: 10.0.0.7
IP: 172.18.0.3
IP: 10.0.1.14
RemoteAddr: 10.0.0.3:54212
GET / HTTP/1.1
Host: 192.168.200.11:8080
User-Agent: curl/7.68.0
Accept: */*
vagrant@swarm-manager:~$ curl 192.168.200.12:8080
Hostname: c83ee052787a
IP: 127.0.0.1
IP: 10.0.0.6
IP: 172.18.0.3
IP: 10.0.1.13
RemoteAddr: 10.0.0.4:49820
GET / HTTP/1.1
Host: 192.168.200.12:8080
User-Agent: curl/7.68.0
Accept: */*
通过上图得知,请求时,会负载均衡到各个节点上
8080这个端口到底映射到哪里了?尝试三个swarm节点的IP加端口8080
可以看到三个节点IP都可以访问,并且回应的容器是不同的(hostname),也就是有负载均衡的效果
vagrant@swarm-manager:~$ curl 192.168.200.10:8080
Hostname: fdf7c1354507
IP: 127.0.0.1
IP: 10.0.0.7
IP: 172.18.0.3
IP: 10.0.1.14
RemoteAddr: 10.0.0.2:36828
GET / HTTP/1.1
Host: 192.168.200.10:8080
User-Agent: curl/7.68.0
Accept: */*
vagrant@swarm-manager:~$ curl 192.168.200.11:8080
Hostname: fdf7c1354507
IP: 127.0.0.1
IP: 10.0.0.7
IP: 172.18.0.3
IP: 10.0.1.14
RemoteAddr: 10.0.0.3:54212
GET / HTTP/1.1
Host: 192.168.200.11:8080
User-Agent: curl/7.68.0
Accept: */*
vagrant@swarm-manager:~$ curl 192.168.200.12:8080
Hostname: c83ee052787a
IP: 127.0.0.1
IP: 10.0.0.6
IP: 172.18.0.3
IP: 10.0.1.13
RemoteAddr: 10.0.0.4:49820
GET / HTTP/1.1
Host: 192.168.200.12:8080
User-Agent: curl/7.68.0
Accept: */*
ingress 数据包的走向
以manager节点为例,数据到底是如何达到service的container的
vagrant@swarm-manager:~$ sudo iptables -nvL -t nat
Chain PREROUTING (policy ACCEPT 388 packets, 35780 bytes)
pkts bytes target prot opt in out source destination
296 17960 DOCKER-INGRESS all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
21365 1282K DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 388 packets, 35780 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 340 packets, 20930 bytes)
pkts bytes target prot opt in out source destination
8 590 DOCKER-INGRESS all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
1 60 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 340 packets, 20930 bytes)
pkts bytes target prot opt in out source destination
2 120 MASQUERADE all -- * docker_gwbridge 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match src-type LOCAL
3 252 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
0 0 MASQUERADE all -- * !docker_gwbridge 172.18.0.0/16 0.0.0.0/0
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
0 0 RETURN all -- docker_gwbridge * 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-INGRESS (2 references)
pkts bytes target prot opt in out source destination
2 120 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.18.0.2:8080
302 18430 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
通过iptables,可以看到一条DNAT的规则,所有访问本地8080端口的流量都被转发到 172.18.0.2:8080
那这个172.18.0.2 是什么?
首先 172.18.0.0/16 这个网段是 docker_gwbridge 的,所以这个地址肯定是连在了 docker_gwbridge 上。
docker network inspect docker_gwbridge 可以看到这个网络连接了一个叫 ingress-sbox 的容器。它的地址就是 172.18.0.2/16
这个 ingress-sbox 其实并不是一个容器,而是一个网络的命名空间 network namespace, 我们可以通过下面的方式进入到这个命名空间
vagrant@swarm-manager:~$ docker run -it --rm -v /var/run/docker/netns:/netns --privileged=true nicolaka/netshoot nsenter --net=/netns/ingress_sbox sh
~ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.2/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
inet 10.0.0.5/32 scope global eth0
valid_lft forever preferred_lft forever
10: eth1@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth1
valid_lft forever preferred_lft forever
通过查看地址,发现这个命名空间连接了两个网络,一个eth1是连接了 docker_gwbridge ,另外一个eth0连接了 ingress 这个网络。
~ # ip route
default via 172.18.0.1 dev eth1
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.2
172.18.0.0/16 dev eth1 proto kernel scope link src 172.18.0.2
~ # iptables -nvL -t mangle
Chain PREROUTING (policy ACCEPT 22 packets, 2084 bytes)
pkts bytes target prot opt in out source destination
12 806 MARK tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 MARK set 0x100
Chain INPUT (policy ACCEPT 14 packets, 1038 bytes)
pkts bytes target prot opt in out source destination
0 0 MARK all -- * * 0.0.0.0/0 10.0.0.5 MARK set 0x100
Chain FORWARD (policy ACCEPT 8 packets, 1046 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 14 packets, 940 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 22 packets, 1986 bytes)
pkts bytes target prot opt in out source destination
~ # ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
FWM 256 rr
-> 10.0.0.6:0 Masq 1 0 0
-> 10.0.0.7:0 Masq 1 0 0
~ #
通过ipvs做了负载均衡
关于这里的负载均衡
- 这是一个stateless load balancing
- 这是三层的负载均衡,不是四层的 LB is at OSI Layer 3 (TCP), not Layer 4 (DNS)
- 以上两个限制可以通过Nginx或者HAProxy LB proxy解决 (docs.docker.com/engine/swar…)
8.6 内部负载均衡和 VIP
- 通过ingress网络可以做到外部负载均衡
- 内部相互访问负载均衡为Internal Load Balancing
创建一个mynet的overlay网络,创建一个service
vagrant@swarm-manager:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
afc8f54c1d07 bridge bridge local
128fd1cb0fae docker_gwbridge bridge local
0ea68b0d28b9 host host local
14fy2l7a4mci ingress overlay swarm
lpirdge00y3j mynet overlay swarm
a8edf1804fb6 none null local
vagrant@swarm-manager:~$ docker service create --name web --network mynet --replicas 2 containous/whoami
jozc1x1c1zpyjl9b5j5abzm0g
overall progress: 2 out of 2 tasks
1/2: running [==================================================>]
2/2: running [==================================================>]
verify: Service converged
vagrant@swarm-manager:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
jozc1x1c1zpy web replicated 2/2 containous/whoami:latest
vagrant@swarm-manager:~$ docker service ps web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
pwi87g86kbxd web.1 containous/whoami:latest swarm-worker1 Running Running 47 seconds ago
xbri2akxy2e8 web.2 containous/whoami:latest swarm-worker2 Running Running 44 seconds ago
vagrant@swarm-manager:~$
创建一个client
vagrant@swarm-manager:~$ docker service create --name client --network mynet xiaopeng163/net-box:latest ping 8.8.8.8
skbcdfvgidwafbm4nciq82env
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
vagrant@swarm-manager:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
skbcdfvgidwa client replicated 1/1 xiaopeng163/net-box:latest
jozc1x1c1zpy web replicated 2/2 containous/whoami:latest
vagrant@swarm-manager:~$ docker service ps client
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
sg9b3dqrgru4 client.1 xiaopeng163/net-box:latest swarm-manager Running Running 28 seconds ago
vagrant@swarm-manager:~$
尝试进入client这个容器,去ping web这个service name, 获取到的IP 10.0.1.30,称之为VIP(虚拟IP)
vagrant@swarm-manager:~$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36dce35d56e8 xiaopeng163/net-box:latest "ping 8.8.8.8" 19 minutes ago Up 19 minutes client.1.sg9b3dqrgru4f14k2tpxzg2ei
vagrant@swarm-manager:~$ docker container exec -it 36dc sh
/omd # curl web
Hostname: 6039865a1e5d
IP: 127.0.0.1
IP: 10.0.1.32
IP: 172.18.0.3
RemoteAddr: 10.0.1.37:40972
GET / HTTP/1.1
Host: web
User-Agent: curl/7.69.1
Accept: */*
/omd # curl web
Hostname: c3b3e99b9bb1
IP: 127.0.0.1
IP: 10.0.1.31
IP: 172.18.0.3
RemoteAddr: 10.0.1.37:40974
GET / HTTP/1.1
Host: web
User-Agent: curl/7.69.1
Accept: */*
/omd # curl web
Hostname: 6039865a1e5d
IP: 127.0.0.1
IP: 10.0.1.32
IP: 172.18.0.3
RemoteAddr: 10.0.1.37:40976
GET / HTTP/1.1
Host: web
User-Agent: curl/7.69.1
Accept: */*
/omd #
/omd # ping web -c 2
PING web (10.0.1.30): 56 data bytes
64 bytes from 10.0.1.30: seq=0 ttl=64 time=0.044 ms
64 bytes from 10.0.1.30: seq=1 ttl=64 time=0.071 ms
--- web ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.044/0.057/0.071 ms
/omd #
这个虚拟IP在一个特殊的网络命令空间里,这个空间连接在我们的mynet这个overlay的网络上
通过 docker network inspect mynet 可以看到这个命名空间,叫lb-mynet
"Containers": {
"36dce35d56e87d43d08c5b9a94678fe789659cb3b1a5c9ddccd7de4b26e8d588": {
"Name": "client.1.sg9b3dqrgru4f14k2tpxzg2ei",
"EndpointID": "e8972d0091afaaa091886799aca164b742ca93408377d9ee599bdf91188416c1",
"MacAddress": "02:42:0a:00:01:24",
"IPv4Address": "10.0.1.36/24",
"IPv6Address": ""
},
"lb-mynet": {
"Name": "mynet-endpoint",
"EndpointID": "e299d083b25a1942f6e0f7989436c3c3e8d79c7395a80dd50b7709825022bfac",
"MacAddress": "02:42:0a:00:01:25",
"IPv4Address": "10.0.1.37/24",
"IPv6Address": ""
}
通过下面的命令,找到这个命名空间的名字
vagrant@swarm-manager:~$ sudo ls /var/run/docker/netns/
1-14fy2l7a4m 1-lpirdge00y dfb766d83076 ingress_sbox lb_lpirdge00
vagrant@swarm-manager:~$
名字叫 lb_lpirdge00
通过nsenter进入到这个命名空间的sh里, 可以看到刚才的VIP地址10.0.1.30
vagrant@swarm-manager:~$ sudo nsenter --net=/var/run/docker/netns/lb_lpirdge00 sh
#
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
50: eth0@if51: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:00:01:25 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.1.37/24 brd 10.0.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet 10.0.1.30/32 scope global eth0
valid_lft forever preferred_lft forever
inet 10.0.1.35/32 scope global eth0
valid_lft forever preferred_lft forever
#
和ingress网络一样,可以查看iptables,ipvs的负载均衡, 基本就可以理解负载均衡是怎么一回事了。 Mark=0x106, 也就是262(十进制),会轮询把请求发给10.0.1.31 和 10.0.1.32
# iptables -nvL -t mangle
Chain PREROUTING (policy ACCEPT 128 packets, 11198 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 92 packets, 6743 bytes)
pkts bytes target prot opt in out source destination
72 4995 MARK all -- * * 0.0.0.0/0 10.0.1.30 MARK set 0x106
0 0 MARK all -- * * 0.0.0.0/0 10.0.1.35 MARK set 0x107
Chain FORWARD (policy ACCEPT 36 packets, 4455 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 101 packets, 7535 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 128 packets, 11198 bytes)
pkts bytes target prot opt in out source destination
# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
FWM 262 rr
-> 10.0.1.31:0 Masq 1 0 0
-> 10.0.1.32:0 Masq 1 0 0
FWM 263 rr
-> 10.0.1.36:0 Masq 1 0 0
#
这个流量会走我们的mynet这个overlay网络。
8.7 swarm stack 部署多service应用
- docker-compose 可以定义多组service,在单机环境下进行部署。在集群环境下可以通过stack命令进行部署多应用
1.在swarm manager节点上安装一下 docker-compose
vagrant@swarm-manager:~$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
vagrant@swarm-manager:~$ sudo chmod +x /usr/local/bin/docker-compose
2.镜像构建
version: "3.8"
services:
flask:
build:
context: ./
dockerfile: Dockerfile
image: flask-redis:latest
ports:
- "8080:5000"
environment:
- REDIS_HOST=redis-server
- REDIS_PASS=${REDIS_PASSWORD}
redis-server:
image: redis:latest
command: redis-server --requirepass ${REDIS_PASSWORD}
docker-compose build
3.通过stack启动服务(可以理解swarm版本的compose)
# docker stack deploy 部署到stack上
vagrant@swarm-manager:~/flask-redis$ env REDIS_PASSWORD=ABC123 docker stack deploy --compose-file docker-compose.yml flask-demo
Ignoring unsupported options: build
Creating network flask-demo_default
Creating service flask-demo_flask
Creating service flask-demo_redis-server
# 查看stack服务
vagrant@swarm-manager:~/flask-redis$ docker stack ls
NAME SERVICES ORCHESTRATOR
flask-demo 2 Swarm
# 显示stack中的service
vagrant@swarm-manager:~/flask-redis$ docker stack ps flask-demo
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
ERROR PORTS
lzm6i9inoa8e flask-demo_flask.1 flask-redis:latest swarm-manager Running Running 23 seconds ago
ejojb0o5lbu0 flask-demo_redis-server.1 redis:latest swarm-worker2 Running Running 21 seconds ago
# 列出stack中的服务
vagrant@swarm-manager:~/flask-redis$ docker stack services flask-demo
ID NAME MODE REPLICAS IMAGE PORTS
mpx75z1rrlwn flask-demo_flask replicated 1/1 flask-redis:latest *:8080->5000/tcp
z85n16zsldr1 flask-demo_redis-server replicated 1/1 redis:latest
# 显示service相关内容
vagrant@swarm-manager:~/flask-redis$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
mpx75z1rrlwn flask-demo_flask replicated 1/1 flask-redis:latest *:8080->5000/tcp
z85n16zsldr1 flask-demo_redis-server replicated 1/1 redis:latest
vagrant@swarm-manager:~/flask-redis$ curl 127.0.0.1:8080
Hello Container World! I have been seen 1 times and my hostname is 21d63a8bfb57.
vagrant@swarm-manager:~/flask-redis$ curl 127.0.0.1:8080
Hello Container World! I have been seen 2 times and my hostname is 21d63a8bfb57.
vagrant@swarm-manager:~/flask-redis$ curl 127.0.0.1:8080
Hello Container World! I have been seen 3 times and my hostname is 21d63a8bfb57.
vagrant@swarm-manager:~/flask-redis$
- docs.docker.com/engine/refe… stack 命令 官方文档
- docker swarm 在创建时 会自动创建overlay网络,部署到集群中去
- 使用单机docker环境 docker compose-up 会创建briger网络
- compose.yml文件需要区分开发环境(docker-compose-dev.yml)和生产环境(docker-compose-prod.yml)
8.8 swarm 中使用 secret 管理敏感数据
-
secret:敏感数据、密码、证书等,进行保护,数据保存在docker RAFT数据库中
创建secret
有两种方式
从标准的收入读取
缺点:密码通过命令显示出来了,可以通过history查询,可以通过清理命令避免
vagrant@swarm-manager:~$ echo abc123 | docker secret create mysql_pass -
4nkx3vpdd41tbvl9qs24j7m6w
vagrant@swarm-manager:~$ docker secret ls
ID NAME DRIVER CREATED UPDATED
4nkx3vpdd41tbvl9qs24j7m6w mysql_pass 8 seconds ago 8 seconds ago
vagrant@swarm-manager:~$ docker secret inspect mysql_pass
[
{
"ID": "4nkx3vpdd41tbvl9qs24j7m6w",
"Version": {
"Index": 4562
},
"CreatedAt": "2021-07-25T22:36:51.544523646Z",
"UpdatedAt": "2021-07-25T22:36:51.544523646Z",
"Spec": {
"Name": "mysql_pass",
"Labels": {}
}
}
]
vagrant@swarm-manager:~$ docker secret rm mysql_pass
mysql_pass
vagrant@swarm-manager:~$
从文件读取
缺点:文件保存在本地,且暴露了密码,当然用完之后清理掉也可以
vagrant@swarm-manager:~$ ls
mysql_pass.txt
vagrant@swarm-manager:~$ more mysql_pass.txt
abc123
vagrant@swarm-manager:~$ docker secret create mysql_pass mysql_pass.txt
elsodoordd7zzpgsdlwgynq3f
vagrant@swarm-manager:~$ docker secret inspect mysql_pass
[
{
"ID": "elsodoordd7zzpgsdlwgynq3f",
"Version": {
"Index": 4564
},
"CreatedAt": "2021-07-25T22:38:14.143954043Z",
"UpdatedAt": "2021-07-25T22:38:14.143954043Z",
"Spec": {
"Name": "mysql_pass",
"Labels": {}
}
}
]
vagrant@swarm-manager:~$
secret 的使用
vagrant@swarm-manager:~$ docker service create --name mysql-demo --secret mysql_pass --env MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_pass mysql:5.7
wb4z2ximgqaefephu9f4109c7
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
vagrant@swarm-manager:~$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
wb4z2ximgqae mysql-demo replicated 1/1 mysql:5.7
vagrant@swarm-manager:~$ docker service ps mysql-demo
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
909429p4uovy mysql-demo.1 mysql:5.7 swarm-worker2 Running Running 32 seconds ago
vagrant@swarm-manager:~$
- 通过创建时携带--sercet 密码内容 ,文件将会存到容器/run/sercrets/目录中以文件形式存在
8.9 swarm使用local volume
- swarm是一个集群,service可能部署在某个节点上,如果volume不是local volume的话,share volum部署在某个节点上需要复杂的配置,docker默认为local volume
- local volume会存在service部署的节点上
- docker stack rm service 时,会删除service、secret、network,但产生的volume不会被删除 本节源码,两个文件
docker-compose.yml
version: "3.8"
services:
db:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_pass
secrets:
- mysql_pass
volumes:
- data:/var/lib/mysql
volumes:
data:
secrets:
mysql_pass:
file: mysql_pass.txt
mysql_pass.txt
vagrant@swarm-manager:~$ more mysql_pass.txt
abc123
vagrant@swarm-manager:~$
- compose定义的存储文件名,会存放在service所在的节点上,显示名称:service_volume定义的名称
执行
docker stack deploy --compose-file docker-compose.yml db
投票APP练习
version: "3"
services:
redis:
image: redis:alpine
networks:
- frontend
deploy:
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
db:
image: postgres:9.4
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
POSTGRES_HOST_AUTH_METHOD: "trust"
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
deploy:
placement:
constraints: [node.role == manager]
vote:
image: dockersamples/examplevotingapp_vote:before
ports:
- 5000:80
networks:
- frontend
depends_on:
- redis
deploy:
replicas: 2
update_config:
parallelism: 2
restart_policy:
condition: on-failure
result:
image: dockersamples/examplevotingapp_result:before
ports:
- 5001:80
networks:
- backend
depends_on:
- db
deploy:
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
worker:
image: dockersamples/examplevotingapp_worker
networks:
- frontend
- backend
depends_on:
- db
- redis
deploy:
mode: replicated
replicas: 1
labels: [APP=VOTING]
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
placement:
constraints: [node.role == manager]
visualizer:
image: dockersamples/visualizer:stable
ports:
- "8080:8080"
stop_grace_period: 1m30s
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
networks:
frontend:
backend:
volumes:
db-data:
_____ _ _____ _
/ ____| (_) / ____| | |
| | __ _ _ | | | |__ ___ _ __
| | / _` | | | | | | '_ \ / _ \ | '_ \
| |____ | (_| | | | | |____ | | | | | __/ | | | |
_____| __,_| |_| _____| |_| |_| ___| |_| |_| | |
好了各位,以上就是这篇文章的全部内容了,能看到这里人啊,都是人才。
如果这个文章写得还不错,觉得「王采臣」我有点东西的话 求点赞👍求关注❤️求分享👥 对耿男我来说真的非常有用!!!
白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!