Docker系统学习之上半章

825 阅读33分钟

为啥学Docker?

  1. 主要是现有的工作上需要用到docker,经常去部署,繁琐的安装浪费太多的时间
  2. 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/

image.png

image.png

image.png

image.png

双击桌面Docker图标,任务栏会出现下图图标:

image.png

image.png

报错了: image.png

image.png 原因是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

image.png 重启电脑,再运行docker就正常了

image.png

打开powershell 验证 具有client 和 server 表示成功:

image.png

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 lsdocker ps
容器的列出(up和exit)docker container ls -adocker 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命令行技巧之全部停止或启动,运行容器强制删除

image.png

# 全部停止容器
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

image.png

  • docker 交互式创建容器 -it
-it 参数为交互模式,此命令运行后,我们会进入shell模式,exit 退出容器交互,随之容器也会停止,
原因主要是因为COMMAND 为sh,结束后 容器也会随之停止
docker container run -it ubuntu sh  

image.png

  • docker exec交互方式(常用)
通过该命令可以进入容器COMMAND中,exit退出,该退出方式仅退出的是shell并不会关闭容器
docker exec -it 容器ID  命令
案例:docker exec -it 5a26 sh  

2.7 容器和虚拟机的区别

image.png

  • 容器其实是进程Containers are just processes
  • 容器中的进程被限制了对CPU内存等资源的访问
  • 当进程停止后,容器就退出了
  • 查看容器进程
docker container top 容器ID 
Liunx查看进程依赖关系:
pstree -halps 进程号

image.png

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 镜像获取方式

image.png 方式一:pull from registry (需联网) 从registry拉取

  • public(公有)
  • private(私有)一般为公司内网环境

方式二:build from Dockerfile (需联网) 从Dockerfile构建

方式三:load from file (需联网) 文件导入 (离线) 通过save保存为压缩文件,通过拷贝到存储设备上,复制到其它机器上,然后通过load的命令进行加载

3.2 Dockerhub网站或Quay.io网站拉取

1.必要条件:需要注册,登录镜像网址搜索镜像

image.png 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", # 镜像支持的系统
  • 下载指定版本镜像

image.png

docker image pull nginx:1.20.0
  • 通过quay.io网址进行拉取

image.png

image.png

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文件

image.png 2.镜像构建

-t:给镜像起名称:版本号,如果不加的话为最新的
docker image build -t 自定义镜像名称  镜像路径(必须有Dockerfile文件)

image.png

image.png

image.png 值得注意的是 容器执行完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

image.png

  • 上传 docker image push

image.png

image.png

4.镜像拉取

docker image pull 账号ID/镜像名称:版本

image.png

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系统特点 

  1. 小巧:基于Musl libcbusybox,和busybox一样小巧,最小的Docker镜像只有5MB;
  2. 安全:面向安全的轻量发行版;
  3. 简单:提供APK包管理工具,软件的搜索、安装、删除、升级都非常方便。
  4. 适合容器使用:由于小巧、功能完备,非常适合作为容器的基础镜像。

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中保持,并在容器中的环境变量里

image.png

  • 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可以用来设置容器启动时默认会执行的命令。

image.png

  • 容器启动时默认执行的命令
  • 如果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 程序

image.png

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

image.png 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所指向的目录

image.png

  • 未添加.dockerignore文件,体积大小

image.png

  • 添加.dockerignore文件

    1.需要在构建dockerfile 同级目录下新建.dockerignore

    2.在.dockerignore中添加忽悠的文件或文件夹名字

    image.png

image.png

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在系统什么位置
  • 指定了持久化路径,容器与宿主机会实时同步,宿主机

image.png

5.0 Data Volume

部分操作需要Linux系统的环境,但是大部分都可以在Windows环境下的Docker进行操作,只有一个操作不行。 环境准备 准备一个Dockerfile 和一个 my-cron的文件,dockerfile可能运行失败,需要翻墙

$ 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 1645608799(1).png
  • docker volume prune 删除本地所有的volume
  • docker volume inspect volume名称 返回有关卷的信息。默认情况下,此命令将所有结果呈现在 JSON 数组中

1645608964(1).png 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

1645609969(1).png

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 多个机器之间的容器共享数据

image.png 官方参考链接 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相互通信。

hostnameipssh usernamessh password
docker-host1192.168.200.10vagrantvagrant
docker-host2192.168.200.11vagrantvagrant
docker-host3192.168.200.12vagrantvagrant

安装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)并敲回车,这个过程后面都发生了什么

image.png

  1. 本地电脑寻找DNS缓存,没有发送DNS请求进行解析,并将域名转换成IP地址

  2. 通过路由转换,将本地IP转换为公有IP,使用公有IP与服务器进行连接和通信,此技术称之为NAT(网络技术转换)

  3. 计算机发送一个 TCP SYN 消息,Web 服务器使用 TCP SYN-ACK 进行响应,计算机发送一个 TCP ACK,俗称 三次握手

  4. 网页浏览器与网页服务器对话

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通

  • 两台电脑通信

image.png

  • 多台电脑通信,引入交换机

image.png

image.png

  • docker提供了网络连接服务,当在宿主机输入 ip addr时 会出现Docker的网络信息

image.png

  • Docker通信模式

image.png

  • 最下部分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

image.png 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 信息

1645685610(1).png

  • 创建容器时自定义网关和子网信息

    • --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

docs.docker.com/compose/com…

7.5 compose 命令行使用,要所在compose文件目录下执行

docker-compose up 启动

image.png

docker-compose up -d 后台启动

image.png

docker-compose ps  查看后台services 运行情况
docker-compose stop  停止compose运行的容器
docker-compose rm 删除compose容器

1646099205(1).png

  • 删除docker-compose 网络
docker network rm 名称
 删除未使用的容器
  • 命名问题,通过docker-compose命名 网络和容器名称都有前缀 前缀默认为文件夹的名称 image.png 可以自定义进行修改
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 镜像构建和拉取

  • 容器未运行状态启动

image.png

  • 通过docker-compos 进行build 构建 1.需要将构建的dockerfile和文件存放到与dockercompose同级目录中

image.png 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 不指定网络,会默认创建一个 桥接 网络

image.png

两个service能相互ping IP、镜像名称、service 名称、外网,这是因为docker container 会写入docker DNS服务中,同时docker-compose会将service 名称写入DNS服务

  • docker-compose 指定网络 单机情况下使用compose,网络默认是bridge

通过配置ipam,指定分配的子网

image.png

7.8 docker-compose 水平扩展 scale

环境清理

删除所有容器和镜像

$ docker container rm -f $(docker container ps -aq)
$ docker system prune -a -f

启动

目录结构

image.png

  • 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进行了负载均衡的操作

image.png

添加nginx,典型服务部署方式

image.png

  • 文件架构

image.png

  • 新增了 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会进行负载均衡转发

image.png 需要重启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同级目录创建

1646125886(1).png

  • 防止.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 验证配置文件

1646126265(1).png

  • 如果.env环境未添加会出现告警

1646126329(1).png

  • 环境变量发生改变,需要重新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

image.png

  • 查询检查日志
docker container inspect 容器ID

image.png image.png

image.png

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

image.png

Swarm的基本架构

image.png

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相关资料

单节点实战练习

  1. 初始化和查看节点信息
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
  1. 创建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

主节点查看


![1646190029(1).png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/08505ac5ad47407bb0cedd5045a58a0a~tplv-k3u1fbpfcp-watermark.image?)

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份 image.png swarm自动分配到不同机器上,需要注意的是,容器出现宕机情况,会自动启动分配的某个节点上

image.png

  • 命令实践
# 扩展一个或多个复制服务
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 自定义名称

image.png

创建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: */*

image.png 通过上图得知,请求时,会负载均衡到各个节点上 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做了负载均衡

image.png 关于这里的负载均衡

  • 这是一个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

image.png 创建一个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
#

image.png 这个流量会走我们的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

有两种方式

从标准的收入读取

缺点:密码通过命令显示出来了,可以通过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 的使用

参考 hub.docker.com/_/mysql

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:
                             _____         _    _____   _
                          / ____|         (_)  / ____| | |
                         | |        __ _   _  | |      | |__     ___   _ __
                         | |       / _` | | | | |      | '_ \   / _ \ | '_ \
                         | |____  | (_| | | | | |____  | | | | |  __/ | | | |
                          _____|  __,_| |_|  _____| |_| |_|  ___| |_| |_| | |

好了各位,以上就是这篇文章的全部内容了,能看到这里人啊,都是人才。

如果这个文章写得还不错,觉得「王采臣」我有点东西的话 求点赞👍求关注❤️求分享👥 对耿男我来说真的非常有用!!!

白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

1476C9C2.gif