大数据 Docker KNOWU 2024-11-28 2024-11-28 容器技术的介绍 注意我们这里所说的容器container是指的一种技术,而Docker只是一个容器技术的实现,或者说让容器技术普及开来的最成功的实现
容器正在引领基础架构的一场新的革命
90年代的PC
00年代的虚拟化
10年代的cloud
11年代的container
什么是container(容器)?
容器是一种快速的打包技术
Package Software into Standardized Units for Development, Shipment and Deployment
为什么容器技术会出现?
容器技术出现之前
容器技术出现之后
容器 vs 虚拟机
Linux Container容器技术的诞生于2008年(Docker诞生于2013年),解决了IT世界里“集装箱运输”的问题。Linux Container(简称LXC)它是一种内核轻量级的操作系统层虚拟化技术。Linux Container主要由Namespace 和Cgroups 两大机制来保证实现
Namespace命名空间主要用于资源的隔离 (诞生于2002年)
Cgroups(Control Groups)就负责资源管理 控制作用,比如进程组使用CPU/MEM的限制,进程组的优先级控制,进程组的挂起和恢复等等。(由Google贡献,2008年合并到了Linux Kernel)
容器的标准化
在2015年,由Google,Docker、红帽等厂商联合发起了OCI(Open Container Initiative)组织,致力于容器技术的标准化
容器运行时标准 (runtime spec)
简单来讲就是规定了容器的基本操作规范,比如如何下载镜像,创建容器,启动容器等。
容器镜像标准(image spec)
主要定义镜像的基本格式。
容器是关乎“速度”
容器会加速你的软件开发
容器会加速你的程序编译和构建
容器会加速你的测试
容器会速度你的部署
容器会加速你的更新
容器会速度你的故障恢复
容器的快速发展和普及
到2020年,全球超过50%的公司将在生产环境中使用container —— Gartner
在 Linux 系统上安装 Docker 使用脚本快速安装
1 2 3 4 5 6 7 8 # 下载安装脚本到get-dcoker.sh,version为可选 curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh --version <VERSION> # 或者快速安装最新版本 curl -fsSL get.docker.com|sh # 确认是否安装成功 sudo systemctl start docker sudo docker version
2.容器的快速上手 docker命令行的认识 docker + 管理的对象(比如容器,镜像) + 具体操作(比如创建,启动,停止,删除)
例如
docker image pull nginx 拉取一个叫nginx的docker image镜像
docker container stop web 停止一个叫web的docker container容器
1 2 3 4 5 6 7 8 docker version 或docker info #查看版本 docker help #查看docker的命令 docker container --help docker container ps #当前运行的容器 docker container ps -a #当前所有的容器 docker container ls #与ps效果一样,ps是老版的命令 docker image ls docker image rm
理解Image vs Container 镜像 vs 容器 image镜像
Docker image是一个 read-only 文件
这个文件包含文件系统,源码,库文件,依赖,工具等一些运行application所需要的文件
可以理解成一个模板
docker image具有分层的概念
container容器
“一个运行中的docker image”
实质是复制image并在image最上层加上一层 read-write 的层 (称之为 container layer ,容器层)
基于同一个image可以创建多个container
docker image的获取
自己制作
从registry拉取(比如docker hub)
配置国内镜像仓库加速拉取,跟maven配置镜像源一个道理
容器的基本操作
docker container 命令小技巧
1 2 3 4 5 6 $ docker container ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cd3a825fedeb nginx "/docker-entrypoint.…" 7 seconds ago Up 6 seconds 80/tcp mystifying_leakey 269494fe89fa nginx "/docker-entrypoint.…" 9 seconds ago Up 8 seconds 80/tcp funny_gauss 34b68af9deef nginx "/docker-entrypoint.…" 12 seconds ago Up 10 seconds 80/tcp interesting_mahavira 7513949674fc nginx "/docker-entrypoint.…" 13 seconds ago Up 12 seconds 80/tcp kind_nobel
方法1
1 $ docker container stop cd3 269 34b 751
方法2
1 2 3 4 5 6 7 8 9 10 11 12 $ docker container stop $(docker container ps -aq) cd3a825fedeb 269494fe89fa 34b68af9deef 7513949674fc $ docker container ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cd3a825fedeb nginx "/docker-entrypoint.…" 30 seconds ago Exited (0) 2 seconds ago mystifying_leakey 269494fe89fa nginx "/docker-entrypoint.…" 32 seconds ago Exited (0) 2 seconds ago funny_gauss 34b68af9deef nginx "/docker-entrypoint.…" 35 seconds ago Exited (0) 2 seconds ago interesting_mahavira 7513949674fc nginx "/docker-entrypoint.…" 36 seconds ago Exited (0) 2 seconds ago kind_nobel $
和批量停止类似,可以使用 docker container rm $(docker container ps -aq)
docker system prune -a -f 可以快速对系统进行清理,删除停止的容器,不用的image,等等
Container Mode 容器运行的各种模式 attach 模式 范例指令: docker container run -p 80:80 nginx
透过这种方式创建容器的话,容器在前台执行
容器的输入输出结果会反映到本地端,本地端的输入输出也会反映到容器,例如能在终端机看到网页浏览器的 log,ctrl + c 会让容器停止执行
一般情况不推荐使用
detach 模式(一般生产中使用) 范例指令:
1 docker container run -d -p 80:80 nginx
1 2 3 4 5 6 7 8 9 10 ~ docker container run -it busybox sh / # / # / # ls bin dev etc home proc root sys tmp usr var / # ps PID USER TIME COMMAND 1 root 0:00 sh 8 root 0:00 ps / # exit
在一个已经运行的容器里执行一个额外的command
1 docker container exec -it
1 2 3 4 5 6 7 8 9 10 11 ➜ ~ docker container run -d nginx 33d2ee50cfc46b5ee0b290f6ad75d724551be50217f691e68d15722328f11ef6 ➜ ~ ➜ ~ docker container exec -it 33d sh # # ls bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var # ➜ ~
容器和虚拟机 Container vs VM
docker container run 背后发生了什么? 1 $ docker container run -d --publish 80:80 --name webhost nginx
在本地查找是否有nginx这个image镜像,但是没有发现
去远程的image registry查找nginx镜像(默认的registry是Docker Hub)
下载最新版本的nginx镜像 (nginx:latest 默认)
基于nginx镜像来创建一个新的容器,并且准备运行
docker engine分配给这个容器一个虚拟IP地址
在宿主机上打开80端口并把容器的80端口转发到宿主机上
启动容器,运行指定的命令(这里是一个shell脚本去启动nginx)
3. 镜像的获取的三种方式
pull from registry (online) 从registry拉取
build from Dockerfile (online) 从Dockerfile构建
load from file (offline) 文件导入 (离线)
镜像的命令查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ docker image Usage: docker image COMMAND Manage images Commands: build Build an image from a Dockerfile history Show the history of an image import Import the contents from a tarball to create a filesystem image inspect Display detailed information on one or more images load Load an image from a tar archive or STDIN ls List images prune Remove unused images pull Pull an image or a repository from a registry push Push an image or a repository to a registry rm Remove one or more images save Save one or more images to a tar archive (streamed to STDOUT by default) tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE Run 'docker image COMMAND --help' for more information on a command.
从registry拉取pull 1 2 3 4 5 docker pull nginx #默认从Docker Hub拉取,如果不指定版本,会拉取最新版 docker pull nginx:1.20.0 # 指定版本 docker pull quay.io/bitnami/nginx #从指定仓库Quay上拉取镜像 docker image ls #镜像的查看 docker image rm id #镜像的删除
从离线导入load 模拟生成一个离线的镜像
1 docker image save nginx:1.20.0 -o nginx.myself
1 docker image load -i nginx.myself
从dockerfile build Dockerfile 介绍 Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.
https://docs.docker.com/engine/reference/builder/
Dockerfile是用于构建docker镜像的文件
Dockerfile里包含了构建镜像所需的“指令”
Dockerfile有其特定的语法规则
Dockerfile的构建 示例:使用centos 脚本打印”hello docker !”
hello.sh
Dockerfile
1 2 3 FROM centos:7 ADD hello.sh / CMD ["sh" , "/hello.sh" ]
示例:部署一个Hello Docker java项目
新建HelloWorld,并将项目打包
1 2 3 4 5 6 public class HelloDocker { public static void main (String[] args) { System.out.println("Hello Docker !" ); } }
新建一个Dockerfile文件
1 2 3 FROM openjdk:8 -jdk-alpineADD *.jar app.jar ENTRYPOINT ["java" ,"-Djava.security.egd=file:/dev/./urandom" ,"-jar" ,"/app.jar" ]
将文件上传到linux服务器上
构建镜像
1 docker image build -t docker-demo-java .
重新打标签
1 2 3 docker image tag docker-demo-java docker-demo-java:2.0 # 实际应按照远程仓库的规则打标签 docker tag [ImageId] registry.cn-shenzhen.aliyuncs.com/gordonchan/quickstart:[镜像版本号]
推送镜像到远程仓库进行共享 1 2 docker login --username=XXXXXXX registry.cn-shenzhen.aliyuncs.com docker push registry.cn-shenzhen.aliyuncs.com/gordonchan/quickstart:[镜像版本号]
拉取镜像并构建容器运行
示例:执行一个Python程序
容器及进程,所以镜像就是一个运行这个进程所需要的环境。
假如我们要在centos:7上运行下面这个hello.py的Python程序
hello.py的文件内容:
第一步,准备Python环境
1 yum install -y python3.9 python3-pip python3.9-dev
第二步,运行hello.py
1 2 $ python3 hello.py hello docker
把上面的程序构建成一个Dockerfile
Dockerfile
1 2 3 4 5 ARG image=centos:7 FROM ${image}RUN yum install -y python3.9 python3-pip python3.9-dev ADD hello.py / CMD ["python3" , "/hello.py" ]
4.Dockerfile命令详解 基础镜像的选择 (FROM) 基本原则
官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选择Dockerfile开源的
固定版本tag而不是每次都使用latest
尽量选择体积小的镜像
1 2 3 4 5 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE bitnami/nginx 1.18.0 dfe237636dde 28 minutes ago 89.3MB nginx 1.21.0-alpine a6eb2a334a9f 2 days ago 22.6MB nginx 1.21.0 d1a364dc548d 2 days ago 133MB
Build一个Nginx镜像
假如我们有一个 index.html 文件
准备一个Dockerfile
1 2 3 FROM nginx:1.21 .0 -alpineADD ../其他/index.html /usr/share/nginx/html/index.html
在image里RUN 执行指令 RUN 主要用于在build Image里执行指令,比如安装软件,下载文件等。
1 2 3 4 5 6 $ apt-get update $ apt-get install 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
Dockerfile
1 2 3 4 5 6 7 FROM ubuntu:20.04 RUN apt-get update RUN apt-get install -y wget RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
镜像的大小和分层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE ipinfo latest 97bb429363fb 4 minutes ago 138MB ubuntu 21.04 478aa0080b60 4 days ago 74.1MB $ docker image history 97b IMAGE CREATED CREATED BY SIZE COMMENT 97bb429363fb 4 minutes ago RUN /bin/sh -c rm -rf ipinfo_2.0.1_linux_amd… 0B buildkit.dockerfile.v0 <missing> 4 minutes ago RUN /bin/sh -c mv ipinfo_2.0.1_linux_amd64 /… 9.36MB buildkit.dockerfile.v0 <missing> 4 minutes ago RUN /bin/sh -c tar zxf ipinfo_2.0.1_linux_am… 9.36MB buildkit.dockerfile.v0 <missing> 4 minutes ago RUN /bin/sh -c wget https://github.com/ipinf… 4.85MB buildkit.dockerfile.v0 <missing> 4 minutes ago RUN /bin/sh -c apt-get install -y wget # bui… 7.58MB buildkit.dockerfile.v0 <missing> 4 minutes ago RUN /bin/sh -c apt-get update # buildkit 33MB buildkit.dockerfile.v0 <missing> 4 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 4 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B <missing> 4 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B <missing> 4 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B <missing> 4 days ago /bin/sh -c #(nop) ADD file:d6b6ba642344138dc… 74.1MB
每一行的RUN命令都会产生一层image layer, 导致镜像的臃肿。
改进版Dockerfile
1 2 3 4 5 6 7 FROM ubuntu:20.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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE ipinfo-new latest fe551bc26b92 5 seconds ago 124MB ipinfo latest 97bb429363fb 16 minutes ago 138MB ubuntu 21.04 478aa0080b60 4 days ago 74.1MB $ docker image history fe5 IMAGE CREATED CREATED BY SIZE COMMENT fe551bc26b92 16 seconds ago RUN /bin/sh -c apt-get update && apt-get… 49.9MB buildkit.dockerfile.v0 <missing> 4 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 4 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B <missing> 4 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B <missing> 4 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B <missing> 4 days ago /bin/sh -c #(nop) ADD file:d6b6ba642344138dc… 74.1MB $
文件复制 (ADD,COPY) 往镜像里复制文件有两种方式,COPY 和 ADD , 我们来看一下两者的不同。
复制普通文件
COPY 和 ADD 都可以把local的一个文件复制到镜像里 ,如果目标目录不存在,则会自动创建
1 2 FROM python:3.9 .5 -alpine3.13 COPY hello.py /app/hello.py
比如把本地的 hello.py 复制到 /app 目录下。 /app这个folder不存在,则会自动创建
复制压缩文件
ADD 比 COPY高级一点的地方就是,如果复制的是一个gzip等压缩文件时,ADD会帮助我们自动去解压缩文件。
1 2 FROM python:3.9 .5 -alpine3.13 ADD hello.tar.gz /app/
如何选择
因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
构建参数和环境变量 (ARG vs ENV) ARG 和 ENV 是经常容易被混淆的两个Dockerfile的语法,都可以用来设置一个“变量”。 但实际上两者有很多的不同。
1 2 3 4 5 6 7 FROM ubuntu:20.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
1 2 3 4 5 6 7 8 FROM ubuntu:20.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
ARG
1 2 3 4 5 6 7 8 FROM ubuntu:20.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
1 2 3 4 5 6 7 8 9 $ 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 0 d9c964947e2 6 seconds ago 124 MB $ docker container run -it ipinfo-arg-2.0.0 root@b64285579756:/ root@b64285579756:/ 2.0 .0 root@b64285579756:/
ARG 用于构建时传递参数,而 ENV 用于在镜像中设置环境变量供容器运行时使用。
容器启动命令 CMD CMD可以用来设置容器启动时默认会执行的命令。
容器启动时默认执行的命令
如果docker container run启动容器时指定了其它命令,则CMD命令会被忽略
如果定义了多个CMD,只有最后一个会被执行。
1 2 3 4 5 6 7 8 FROM ubuntu:20.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
1 2 3 4 5 6 7 8 $ docker image build -t ipinfo . $ docker container run -it ipinfo root@8cea7e5e8da8:/# root@8cea7e5e8da8:/# root@8cea7e5e8da8:/# root@8cea7e5e8da8:/# pwd / root@8cea7e5e8da8:/#
默认进入到shell是因为在ubuntu的基础镜像里有定义CMD
1 2 3 4 5 6 7 8 9 $ 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
容器启动命令 ENTRYPOINT ENTRYPOINT 也可以设置容器启动时要执行的命令,但是和CMD是有区别的。
CMD 设置的命令,可以在docker container run 时传入其它命令,覆盖掉 CMD 的命令,但是 ENTRYPOINT 所设置的命令是一定会被执行的。
ENTRYPOINT 和 CMD 可以联合使用,ENTRYPOINT 设置执行的命令,CMD传递参数
1 2 FROM ubuntu:20.04 CMD ["echo" , "hello docker" ]
把上面的Dockerfile build成一个叫 demo-cmd 的镜象
1 2 3 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE demo-cmd latest 5bb63bb9b365 8 days ago 74.1MB
1 2 FROM ubuntu:20.04 ENTRYPOINT ["echo" , "hello docker" ]
build成一个叫 demo-entrypoint 的镜像
1 2 3 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE demo-entrypoint latest b1693a62d67a 8 days ago 74.1MB
CMD的镜像,如果执行创建容器,不指定运行时的命令,则会默认执行CMD所定义的命令,打印出hello docker
1 2 $ docker container run -it --rm demo-cmd hello docker
但是如果我们docker container run的时候指定命令,则该命令会覆盖掉CMD的命令,如:
1 2 $ docker container run -it --rm demo-cmd echo "hello world" hello world
但是ENTRYPOINT的容器里ENTRYPOINT所定义的命令则无法覆盖,一定会执行
1 2 3 4 5 $ docker container run -it --rm demo-entrypoint hello docker $ docker container run -it --rm demo-entrypoint echo "hello world" hello docker echo hello world $
Shell 格式和 Exec 格式 容器的启动命令中带有参数传递的处理方式
CMD和ENTRYPOINT同时支持shell格式和Exec格式。
Shell格式
1 ENTRYPOINT echo "hello docker"
Exec格式
以可执行命令的方式
1 ENTRYPOINT ["echo", "hello docker"]
1 CMD ["echo", "hello docker"]
注意shell脚本的问题
1 2 3 FROM ubuntu:20.04 ENV NAME=dockerCMD echo "hello $NAME "
假如我们要把上面的CMD改成Exec格式,下面这样改是不行的, 大家可以试试。
1 2 3 FROM ubuntu:20.04 ENV NAME=dockerCMD ["echo" , "hello $NAME " ]
它会打印出 hello $NAME , 而不是 hello docker ,那么需要怎么写呢? 我们需要以shell脚本的方式去执行
1 2 3 FROM ubuntu:20.04 ENV NAME=dockerCMD ["sh" , "-c" , "echo hello $NAME " ]
实战:构建一个 Python Flask 镜像 Python 程序
1 2 3 4 5 6 7 8 from flask import Flaskapp = Flask(__name__) @app.route('/' ) def hello_world (): return 'Hello, World!'
Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 FROM python:3.9 .5 -slimCOPY app.py /src/app.py RUN pip install flask WORKDIR /src ENV FLASK_APP=app.pyEXPOSE 5000 CMD ["flask" , "run" , "-h" , "0.0.0.0" ]
1 2 docker image build -t flask . docker run -d -p 5000:5000 flask
Dockerfile 技巧——合理使用缓存 如果按照上述Dockerfile,修改app.py后,之后的操作比如install flask都不会使用cache,这样影响构建效率,因此,把经常改变的内容放在后面,尽可能的使用缓存,提高构建速度。
1 2 3 4 5 6 7 FROM python:3.9 .5 -slimRUN pip install flask WORKDIR /src ENV FLASK_APP=app.pyEXPOSE 5000 COPY app.py /src/app.py CMD ["flask" , "run" , "-h" , "0.0.0.0" ]
Dockerfile 技巧——合理使用 .dockerignore 什么是Docker build context
Docker是client-server架构,理论上Client和Server可以不在一台机器上。
在构建docker镜像的时候,需要把所需要的文件由CLI(client)发给Server,这些文件实际上就是build context
加入文件夹下有些文件不是必须的,可以过滤掉。
Dockerfile
1 2 3 4 5 6 7 FROM python:3.9 .5 -slimRUN pip install flask WORKDIR /src ENV FLASK_APP=app.pyEXPOSE 5000 COPY ./ /src/ CMD ["flask" , "run" , "-h" , "0.0.0.0" ]
增加vim .dockerignore
有了.dockerignore文件后,我们再build, build context就小了很多
Dockerfile 技巧——镜像的多阶段构建 这一节来聊聊多阶段构建,以及为什么要使用它。
假如有一个C的程序,我们想用Docker去做编译,然后执行可执行文件。
1 2 3 4 5 6 #include <stdio.h> void main (int argc, char *argv[]) { printf ("hello %s\n" , argv[argc - 1 ]); }
本地测试(如果你本地有C环境)
1 2 3 4 5 6 7 8 9 10 $ gcc --static -o hello hello.c $ ls hello hello.c $ ./hello docker hello docker $ ./hello world hello world $ ./hello friends hello friends $
构建一个Docker镜像,因为要有C的环境,所以我们选择gcc这个image
1 2 3 4 5 6 7 8 9 10 11 FROM gcc:9.4 COPY hello.c /src/hello.c WORKDIR /src RUN gcc --static -o hello hello.c ENTRYPOINT [ "/src/hello" ] CMD []
build和测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 $ docker build -t hello . Sending build context to Docker daemon 5.12kB Step 1/6 : FROM gcc:9.4 ---> be1d0d9ce039 Step 2/6 : COPY hello.c /src/hello.c ---> Using cache ---> 70a624e3749b Step 3/6 : WORKDIR /src ---> Using cache ---> 24e248c6b27c Step 4/6 : RUN gcc --static -o hello hello.c ---> Using cache ---> db8ae7b42aff Step 5/6 : ENTRYPOINT [ "/src/hello" ] ---> Using cache ---> 7f307354ee45 Step 6/6 : CMD [] ---> Using cache ---> 7cfa0cbe4e2a Successfully built 7cfa0cbe4e2a Successfully tagged hello:latest $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello latest 7cfa0cbe4e2a 2 hours ago 1.14GB gcc 9.4 be1d0d9ce039 9 days ago 1.14GB $ docker run --rm -it hello docker hello docker $ docker run --rm -it hello world hello world $ docker run --rm -it hello friends hello friends $
可以看到镜像非常的大,1.14GB
实际上当我们把hello.c编译完以后,并不需要这样一个大的GCC环境,一个小的alpine镜像就可以了。
这时候我们就可以使用多阶段构建了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 FROM gcc:9.4 AS builderCOPY 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 []
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 $ docker build -t hello-apline -f Dockerfile-new . Sending build context to Docker daemon 5.12kB Step 1/8 : FROM gcc:9.4 AS builder ---> be1d0d9ce039 Step 2/8 : COPY hello.c /src/hello.c ---> Using cache ---> 70a624e3749b Step 3/8 : WORKDIR /src ---> Using cache ---> 24e248c6b27c Step 4/8 : RUN gcc --static -o hello hello.c ---> Using cache ---> db8ae7b42aff Step 5/8 : FROM alpine:3.13.5 ---> 6dbb9cc54074 Step 6/8 : COPY --from=builder /src/hello /src/hello ---> Using cache ---> 18c2bce629fb Step 7/8 : ENTRYPOINT [ "/src/hello" ] ---> Using cache ---> 8dfb9d9d6010 Step 8/8 : CMD [] ---> Using cache ---> 446baf852214 Successfully built 446baf852214 Successfully tagged hello-apline:latest $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-alpine latest 446baf852214 2 hours ago 6.55MB hello latest 7cfa0cbe4e2a 2 hours ago 1.14GB demo latest 079bae887a47 2 hours ago 125MB gcc 9.4 be1d0d9ce039 9 days ago 1.14GB $ docker run --rm -it hello-alpine docker hello docker $ docker run --rm -it hello-alpine world hello world $ docker run --rm -it hello-alpine friends hello friends $
可以看到这个镜像非常小,只有6.55MB
使用的openjdk:8-jdk-alpine就是瘦身过的镜像,不过要注意时区问题,要自己设置。
Dockerfile 技巧——尽量使用非root用户 Root的危险性
docker的root权限一直是其遭受诟病的地方,docker的root权限有那么危险么?我们举个例子。
假如我们有一个用户,叫demo,它本身不具有sudo的权限,所以就有很多文件无法进行读写操作,比如/root目录它是无法查看的。
1 2 3 4 [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里。
1 2 3 4 5 6 [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里,你就可以自由进行查看了。
1 2 3 4 5 6 7 8 9 [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权限
1 2 3 4 [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 ~]$
但是我可以给自己添加。
1 2 3 4 [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权限了。
1 2 3 [demo@docker-host ~]$ sudo more /etc/sudoers | grep demo demo ALL=(ALL) ALL [demo@docker-host ~]$
如何使用非root用户
我们准备两个Dockerfile,第一个Dockerfile如下,
1 2 3 4 5 6 7 8 9 10 11 12 FROM python:3.9 .5 -slimRUN pip install flask COPY app.py /src/app.py WORKDIR /src ENV FLASK_APP=app.pyEXPOSE 5000 CMD ["flask" , "run" , "-h" , "0.0.0.0" ]
假设构建的镜像名字为 flask-demo
第二个Dockerfile,使用非root用户来构建这个镜像,名字叫 flask-no-root Dockerfile如下:
通过groupadd和useradd创建一个flask的组和用户
通过USER指定后面的命令要以flask这个用户的身份运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 FROM python:3.9 .5 -slimRUN pip install flask && \ groupadd -r flask && useradd -r -g flask flask && \ mkdir /src && \ chown -R flask:flask /src USER flaskCOPY app.py /src/app.py WORKDIR /src ENV FLASK_APP=app.pyEXPOSE 5000 CMD ["flask" , "run" , "-h" , "0.0.0.0" ]
1 2 3 4 5 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE flask-no-root latest 80996843356e 41 minutes ago 126MB flask-demo latest 2696c68b51ce 49 minutes ago 125MB python 3.9.5-slim 609da079b03a 2 weeks ago 115MB
分别使用这两个镜像创建两个容器
1 2 3 4 5 6 7 8 $ docker run -d --name flask-root flask-demo b31588bae216951e7981ce14290d74d377eef477f71e1506b17ee505d7994774 $ docker run -d --name flask-no-root flask-no-root 83aaa4a116608ec98afff2a142392119b7efe53617db213e8c7276ab0ae0aaa0 $ docker container ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 83aaa4a11660 flask-no-root "flask run -h 0.0.0.0" 4 seconds ago Up 3 seconds 5000/tcp flask-no-root b31588bae216 flask-demo "flask run -h 0.0.0.0" 16 seconds ago Up 15 seconds 5000/tcp f
5.Docker的存储 Data Volume 示例:保存数据了解数据是如何持久化?
环境准备
准备一个Dockerfile 和一个 my-cron的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ ls Dockerfile my-cron $ more Dockerfile 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"] $ $ more my-cron */1 * * * * date >> /app/test.txt
工具supercronic:https://github.com/aptible/supercronic/ 这个专为容器而生的计划任务工具。
my-cron的意思是使用的my-cron就是一个crontab格式的计划任务,比如, 每隔一分钟时间输出到一个文件里
构建镜像
1 2 3 4 $ docker image build -t my-cron . $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE my-cron latest e9fbd9a562c9 4 seconds ago 24.7MB
创建容器(不指定-v参数),在这个Volume的mountpoint可以发现容器创建的文件。此时Docker会自动创建一个随机名字的volume,去存储我们在Dockerfile定义的volume VOLUME ["/app"]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ docker run -d my-cron 9a8fa93f03c42427a498b21ac520660752122e20bcdbf939661646f71d277f8f $ docker volume ls DRIVER VOLUME NAME local 043a196c21202c484c69f2098b6b9ec22b9a9e4e4bb8d4f55a4c3dce13c15264 $ docker volume inspect 043a196c21202c484c69f2098b6b9ec22b9a9e4e4bb8d4f55a4c3dce13c15264 [ { "CreatedAt": "2021-06-22T23:06:13+02:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/043a196c21202c484c69f2098b6b9ec22b9a9e4e4bb8d4f55a4c3dce13c15264/_data", "Name": "043a196c21202c484c69f2098b6b9ec22b9a9e4e4bb8d4f55a4c3dce13c15264", "Options": null, "Scope": "local" } ]
创建容器(指定-v参数)
在创建容器的时候通过 -v 参数我们可以手动的指定需要创建Volume的名字,以及对应于容器内的路径,这个路径是可以任意的,不必需要在Dockerfile里通过VOLUME定义
比如我们把上面的Dockerfile里的VOLUME删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 # RUN cron job CMD ["/usr/local/bin/supercronic", "/app/my-cron"]
重新build镜像,然后创建容器,加-v参数 容器外路径:容器内路径
1 2 docker image build -t my-cron . docker container run -d -v cron-data:/app my-cron
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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/_datamy-cron $ ls /var/lib/docker/volumes/cron-data/_datamy-cron test.txt
环境清理
强制删除所有容器,系统清理和volume清理
1 2 3 $ docker rm -f $(docker container ps -aq) $ docker system prune -f $ docker volume prune -f
Data Volume 练习 MySQL 使用MySQL官方镜像,tag版本5.7
Dockerfile可以在这里查看 https://github.com/docker-library/mysql/tree/master/5.7
准备镜像
创建容器
关于MySQL的镜像使用,可以参考dockerhub https://hub.docker.com/_/mysql?tab=description&page=1&ordering=last_updated
关于Dockerfile Volume的定义,可以参考 https://github.com/docker-library/mysql/tree/master/5.7
1 2 # --name 容器名docker-mysql -e 编辑密码 -v磁盘映射到docker下的mysql-data目录下 docker container run --name docker-mysql -e MYSQL_ROOT_PASSWORD=123456 -d -v mysql-data:/var/lib/mysql mysql:5.7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ 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
1 2 docker container exec -it 022 sh mysql -u root -p
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ 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/_dataauto.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 $
多个机器之间的容器共享数据
官方参考链接 https://docs.docker.com/storage/volumes/#share-data-among-machines
Docker的volume支持多种driver。默认创建的volume driver都是local
1 2 3 4 5 6 7 8 9 10 11 12 $ 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
在其中两台机器上安装一个plugin vieux/sshfs
1 2 3 4 5 [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
1 2 3 4 5 [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
1 2 3 4 [vagrant@docker-host1 ~]$ docker volume create --driver vieux/sshfs \ -o sshcmd=vagrant@192.168.200.12:/home/vagrant \ -o password=vagrant \ sshvolume
查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [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文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [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上看到
1 2 3 4 5 6 [vagrant@docker-host3 ~]$ pwd /home/vagrant [vagrant@docker-host3 ~]$ ls test.txt [vagrant@docker-host3 ~]$ more test.txt this is ssh volume
如何给已创建的容器额外挂载共享文件夹
一、背景
在使用docker过程中,有时候创建容器时候没有设置挂载本地数据卷进行文件夹共享,但已经在容器中配置完了环境,此时再重新创建一个容器非常麻烦,因此需要对已有的容器挂载数据卷。
二、挂载原理
Docker中所有的容器的配置,如挂载点、运行方式等都是以json文件进行配置,修改对应的json文件参数即可挂载指定文件夹。
配置容器的json文件 /var/lib/docker/containers/<容器ID>/config.v2.json /var/lib/docker/containers/<容器ID>/hostconfig.json
三、打开文件
使用 docker ps -a拿到需要更改的container的12位ID,然后docker inspect id,拿到64位ID(终端最上面的那个ID)
停止所有container 并使用service docker stop关闭docker服务(必须关闭Docker服务,否则无法修改成功 )
到目录/var/lib/docker/containers/<64位容器ID>/中复制 config.v2.json和 hostconfig.json两个文件到任意不用root权限的目录下,同时对原文件进行备份。
4.在~/下新建两个同名文件(避免权限问题)cd ~/
touch config.v2.json hostconfig.json
5.新开一个终端,进入到容器目录下,打开文件 sudo -i ,提升权限 cd /var/lib/docker/containers/<64位容器ID>/
修改前一定要先备份下,否则改错了就GG! cp config.v2.json config.v2.json.back cp hostconfig.json hostconfig.back
四、添加共享文件夹挂载信息
打开**~/目录**下的这两个文件
1.修改config.v2.json文件
在MountPoints参数下按照相应的格式进行添加相应的字段,注意,必须是绝对路径,且不能是/root,必须是/root/的二级子目录 。
2.修改hostconfig.json文件
在hostconfig.json文件中的Binds参数添加宿主机和容器共享文件夹目录(注意,必须是绝对路径,且不能是/root,必须是/root/dataset这样的二级子目录 )
五、修改容器配置
把 ~/目录下的config.v2.json和hostconfig.json两个文件内容,对应复制到以下文件中。 /var/lib/docker/containers/<容器ID>/config.v2.json /var/lib/docker/containers/<容器ID>/hostconfig.json
六、启动docker 服务
6.Docker网络 Bridge 网络
容器间通信 两个容器都连接到了一个叫 docker0 的Linux bridge上
创建两个容器
1 2 3 docker container run -d --name box1 busybox /bin/sh -c "while true; do sleep 3600; done" docker container run -d --name box2 busybox /bin/sh -c "while true; do sleep 3600; done"
1 2 3 4 docker network ls docker network inspect bridge # 查看网络 brctl show
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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": {} } ]
1 2 3 4 5 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
查看路由
1 2 3 4 5 $ ip route default via 10.0.2.2 dev eth0 proto dhcp metric 100 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 192.168.200.0/24 dev eth1 proto kernel scope link src 192.168.200.10 metric 101
外部访问容器:端口转发 不同host主机之间的容器通信
创建容器
1 2 3 4 5 6 7 docker container run -d --rm --name web -p 8080:80 nginx:1.20.0 # 暴露端口,外部访问容器 firewall-cmd --zone=public --add-port=80/tcp --permanent # 重启防火墙 systemctl restart firewalld # 查看已经对外暴露的端口,确认端口是否已经暴露 firewall-cmd --zone=public --list-ports
测试容器间是否通信,让box1去请求web,通信返回一个index文件。
1 docker container exec -it box1 wget http://172.17.0.5
测试外部网络是否能访问容器,访问web暴露出来的8080端口
同一host暴露出来的端口需唯一,不然后起的服务报错,端口被占用 。这样就保证外部所访问的容器是唯一的。
1 docker container run -d --rm --name web2 -p 8080:80 nginx:1.20.0
如果network采用host模式,暴露出来是默认的80端口
1 docker container run -d --rm --name web1 --network host nginx:1.20.0
同一host中的不同bridge通信 创建和使用 bridge
在上面的启动的容器中,断开web的连接
1 docker network disconnect bridge web
创建一个自己的mybridge
1 docker network create mybridge
web连接mybridge,要实现box1能通信web,box1也得连接mybridge,而box2是ping不同web。
1 2 3 docker network connect mybridge web docker container exec -it box1 ping web docker network connect mybridge box1
创建时可指定路由和ip范围
1 docker network create -d bridge --gateway 172.200.0.1 --subnet 172.200.0.0/16 detailbridge
容器通信的底层原理: —————————————–网络命名空间和虚拟网络设备对
Linux的Namespace(命名空间)技术是一种隔离技术,常用的Namespace有 user namespace, process namespace, network namespace等
网络命名空间 是 Linux 内核用来隔离不同容器间的网络资源(每个 Docker 容器都拥有一个独立的网络命名空间),网络命名空间主要隔离的资源包括:
1.iptables规则表
2.路由规则表
3.网络设备列表
如下图所示,当系统中拥有 3 个网络命名空间:
由于不同的网络命名空间之间是相互隔离的,所以不同的网络命名空间之间并不能直接通信。比如在 网络命名空间A 配置了一个 IP 地址为 172.17.42.1 的设备,但在 网络命名空间B 里却不能访问,如下图所示:
就好比两台电脑,如果没有任何网线连接,它们之间是不能通信的。所以,Linux 内核提供了 虚拟网络设备对(veth) 这个功能,用于解决不同网络命名空间之间的通信。
Docker 就是使用 虚拟网络设备对 来实现不同容器之间的通信,其原理如下图:
以下模拟的是容器的通信原理,即不同命名空间的通信
新建增加网络命名空间脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # !/bin/bash bridge=$1 namespace=$2 addr=$3 # 虚拟网络设备对 # 在docker0上 vethA=veth-$namespace # 在容器内 vethB=eth00-$namespace # 新建命名空间 sudo ip netns add $namespace # 创建一个veth pair sudo ip link add $vethA type veth peer name $vethB # 设置虚拟网络设备所属命名空间 sudo ip link set $vethB netns $namespace # 给虚拟网络设备添加ip sudo ip netns exec $namespace ip addr add $addr dev $vethB # 开启网络命令空间 namespace vethB 端口 sudo ip netns exec $namespace ip link set $vethB up sudo ip link set $vethA up sudo brctl addif $bridge $vethA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 brctl --help查看使用命令 Usage: brctl [commands] commands: addbr <bridge> add bridge delbr <bridge> delete bridge addif <bridge> <device> add interface to bridge delif <bridge> <device> delete interface from bridge hairpin <bridge> <port> {on|off} turn hairpin on/off setageing <bridge> <time> set ageing time setbridgeprio <bridge> <prio> set bridge priority setfd <bridge> <time> set bridge forward delay sethello <bridge> <time> set hello time setmaxage <bridge> <time> set max message age setpathcost <bridge> <port> <cost> set path cost setportprio <bridge> <port> <prio> set port priority show [ <bridge> ] show a list of bridges showmacs <bridge> show a list of mac addrs showstp <bridge> show bridge stp info stp <bridge> {on|off} turn stp on/off [root@container1 docker]# ip netns help Usage: ip netns list ip netns add NAME ip netns set NAME NETNSID ip [-all] netns delete [NAME] ip netns identify [PID] ip netns pids NAME ip [-all] netns exec [NAME] cmd ... ip netns monitor ip netns list-id
启动两个容器
1 2 3 docker container run -d --name box3 --network mybridge busybox /bin/sh -c "while true; do sleep 3600; done" docker container run -d --name box4 --network mybridge busybox /bin/sh -c "while true; do sleep 3600; done"
获取bridge name
设置命名空间
1 2 3 4 5 6 7 8 sh add-ns-to-br.sh br-1379584d9670 ns1 172.19.0.2/16 sh add-ns-to-br.sh br-1379584d9670 ns2 172.19.0.3/16 # 查看网络命名空间list sudo ip netns ls # 进入命令空间查看ip信息 sudo ip netns exec ns2 ip a ping 172.19.0.2
以上就是模拟实现了不同网络命名空间之间的通信。
断开虚拟网络设备,不同的网络命名空间就ping不通,实现隔离。
1 sudo ip link set veth-ns1 down
1 2 3 4 5 6 7 8 9 10 11 12 # 重启网卡 service network restart # 查看配置信息 ifconfig # 错误日志查看 cat /var/log/messages | grep network # 查看docker的情况 systemctl status docker.service # 关掉网桥 ifconfig docker0 down # 删除网桥 brctl delbr docker0
7.Docker Compose docker compose 介绍 要启动一些容器需进行一系列操作,一般我们使用shell脚本进行一键式操作。而基于这个需求,docker compose就是专门用于解决这种组合的操作。通过配置yml文件来实现。
docker compose 的安装 Windows和Mac在默认安装了docker desktop以后,docker-compose随之自动安装
1 2 PS C:\Users\Peng Xiao\docker.tips> docker-compose --version docker-compose version 1.29.2, build 5becea4c
Linux用户需要自行安装
最新版本号可以在这里查询 https://github.com/docker/compose/releases
1 2 3 4 $ 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
1 2 3 # 快速安装对应版本pip wget https://bootstrap.pypa.io/pip/3.6/get-pip.py python3 get-pip.py
1 pip install docker-compose
docker-compose基本使用 compose 文件的结构和版本
docker compose文件的语法说明 https://docs.docker.com/compose/compose-file/
基本语法结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 version: "3.8" services: servicename: image: command: environment: volumes: networks: ports: servicename2: volumes: networks:
以 Python Flask + Redis练习:为例子,改造成一个docker-compose文件
自定义的flask-demo
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Flaskfrom redis import Redisimport osimport socketapp = 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FROM python:3.9 .5 -slimRUN python -m pip install --upgrade pip && \ pip install flask redis && \ groupadd -r flask && useradd -r -g flask flask && \ mkdir /src && \ chown -R flask:flask /src USER flaskWORKDIR /src ENV FLASK_APP=app.py REDIS_HOST=redisCOPY app.py /src/app.py EXPOSE 5000 CMD ["flask" , "run" , "-h" , "0.0.0.0" ]
未使用docker-compose之前的操作
1 2 3 4 5 6 7 8 9 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 5000:5000 flask-demo
只构建flask-demo镜像,后面可以直接拉取
1 docker image build -t flask-demo .
docker-compose.yml 文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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:
基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Usage: docker-compose [-f <arg>...] [--profile <name>...] [options] [--] [COMMAND] [ARGS...] docker-compose -h|--help Commands: build Build or rebuild services config Validate and view the Compose file create Create services down Stop and remove resources events Receive real time events from containers exec Execute a command in a running container help Get help on a command images List images kill Kill containers logs View output from containers pause Pause services port Print the public port for a port binding ps List containers pull Pull service images push Push service images restart Restart services rm Remove stopped containers run Run a one-off command scale Set number of containers for a service start Start services stop Stop services top Display the running processes unpause Unpause services up Create and start containers version Show version information and quit
1 2 3 4 5 6 # 确认配置 docker-compose config # 从配置build的Dockerfile构建镜像,并不会启动容器,可以用于构建镜像或者更新镜像,没配置直接拉取镜像 docker-compose build # 获取镜像启动服务,镜像不存在,按情况从仓库拉取或build docker-compose up -d
1 2 3 4 # 查看compose中的相关镜像,只显示该compose服务包含的镜像 docker-compose images # 查看容器的运行情况 docker-compose ps
1 2 3 4 5 6 # 对容器的操作就是docker的操作 docker ps docker exec -it 0b sh # 对redis进行读写数据 set k1 v1 get k1
1 2 3 4 # 停止服务 docker-compose stop # 移除停止container docker-compose rm
docker-compose 服务的构建、拉取与更新 带有build的yml,会视情况构建镜像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 version: "3.8" services: flask-demo: image: flask-demo:latest build: context: . dockerfile: Dockerfile environment: - REDIS_HOST=redis-server networks: - demo-network ports: - 8080 :5000 redis-server: image: redis:latest networks: - demo-network networks: demo-network:
1 2 3 4 # 根据Dockerfile重新下载需要的镜像并构建容器,并启动 docker-compose up --build -d # 重新启动单个容器 docker-compose up -d --build flask
根据Dockerfile重新下载需要的镜像并构建容器,也就是说这句相当于是 docker-compose build –no-cache 和 docker-compose up -d 的集合体, 意味着构建镜像的时候是根据Dockerfile的最新内容来的,而不会使用缓存,这样就避免了构建镜像时由于缓存造成的影响。
docker-compose 网络 如果配置没指定网络,则创建默认的网络docker-compose_default(文件目录_default)
1 2 3 4 5 version: "3.8" services: box1: image: busybox
多网络的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 version: "3.8" services: box1: image: busybox command: /bin/sh -c "while true; do sleep 3600; done" networks: - demo-network1 box2: image: busybox command: /bin/sh -c "while true; do sleep 3600; done" networks: - demo-network2 box3: image: busybox command: /bin/sh -c "while true; do sleep 3600; done" networks: - demo-network1 - demo-network2 networks: demo-network1: ipam: driver: default config: - subnet: 172.28 .0 .0 /16 demo-network2:
按照配置,box1和box2 ping不通,都能和box3通信
水平扩展 scale 去掉端口指定,不然端口占用,启动失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 version: "3.8" services: flask: build: context: ../flask dockerfile: Dockerfile image: flask-demo:latest environment: - REDIS_HOST=redis-server networks: - demo-network redis-server: image: redis:latest networks: - demo-network client: image: busybox command: /bin/sh -c "while true; do sleep 3600; done" networks: - demo-network networks: demo-network:
1 docker-compose up -d --build --scale flask=3
与服务器通信
1 2 3 4 5 ping flask # busybox没有curl命令使用wget wget -O- flask:5000 # 也可以用封装好的image xiaopeng163/net-box:latest
wget -O-以’-‘作为file参数,那么数据将会被打印到标准输出,通常为控制台。
外部如何访问,flask端口不能写死暴露?
通过nginx反向代理
nginx.conf 配置文件
1 2 3 4 5 6 server { listen 80 default_server; location / { proxy_pass http://flask:5000; } }
yml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 version: "3.8" services: flask: build: context: ../flask dockerfile: Dockerfile image: flask-demo:latest environment: - REDIS_HOST=redis-server depends_on: - redis-server networks: - frontend - backend 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 - ../log/nginx:/var/log/nginx networks: - frontend networks: backend: frontend:
服务的依赖:nginx依赖flask,flask依赖redis,通过depend_on配置来定义程序启动顺序
和 ports的区别是, expose不会将端口暴露给主机,主机无法访问 expose的端口。
.dockerignore
1 2 3 4 5 6 .env .dockerignore Dockerfile docker-compose.yaml nginx.conf var
1 2 docker-compose config docker-compose up -d --build --scale flask=3
docker compose 环境变量 上述涉及的redis若添加密码,无论写到代码里还是配置里都是不安全的,故可以写到.env里面,.env文件.dockerignore文件不上传。
.env
yml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 version: "3.8" services: flask: build: context: ../flask dockerfile: Dockerfile image: flask-demo:latest environment: - REDIS_HOST=redis-server - REDIS_PASS=${REDIS_PASSWORD} networks: - frontend - backend 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 - ../log/nginx:/var/log/nginx networks: - frontend networks: backend: frontend:
app.py增加获取该变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from flask import Flaskfrom redis import StrictRedisimport osimport socketapp = 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"
docker compose 健康检查 Dockerfile healthcheck https://docs.docker.com/engine/reference/builder/#healthcheck
docker compose https://docs.docker.com/compose/compose-file/compose-file-v3/#healthcheck
健康检查是容器运行状态的高级检查,主要是检查容器所运行的进程是否能正常的对外提供“服务”,比如一个数据库容器,我们不光 需要这个容器是up的状态,我们还要求这个容器的数据库进程能够正常对外提供服务,这就是所谓的健康检查。
容器的健康检查
容器本身有一个健康检查的功能,但是需要在Dockerfile里定义,或者在执行docker container run 的时候,通过下面的一些参数指定
1 2 3 4 5 6 7 8 9 10 --health-cmd string Command to run to check health --health-interval duration Time between running the check (ms|s|m|h) (default 0s) --health-retries int Consecutive failures needed to report unhealthy --health-start-period duration Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s) --health-timeout duration Maximum time to allow one check to
Dockerfile healthcheck示例源码
我们以下面的这个flask容器为例,Dockerfile如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 FROM python:3.9 .5 -slimRUN 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 flaskCOPY app.py /src/app.py WORKDIR /src ENV FLASK_APP=app.py REDIS_HOST=redisEXPOSE 5000 HEALTHCHECK --interval=30s --timeout =3s \ CMD curl -f http://localhost:5000/ || exit 1 CMD ["flask" , "run" , "-h" , "0.0.0.0" ]
上面Dockerfili里的HEALTHCHECK 就是定义了一个健康检查。 会每隔30秒检查一次,如果失败就会退出,退出代码是1
查看容器状态
1 2 3 $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 059c12486019 flask-demo "flask run -h 0.0.0.0" 4 hours ago Up 8 seconds (health: starting) 5000/tcp dazzling_tereshkova
也可以通过docker container inspect 059 查看详情, 其中有有关health的
1 2 3 4 5 6 7 8 9 10 11 12 "Health": { "Status": "starting", "FailingStreak": 1, "Log": [ { "Start": "2021-07-14T19:04:46.4054004Z", "End": "2021-07-14T19:04:49.4055393Z", "ExitCode": -1, "Output": "Health check exceeded timeout (3s)" } ] }
模拟实现故障,把redis停掉之后,经过3次检查,一直是不通的,然后health的状态会从starting变为 unhealthy
重启redis,经过几秒钟,我们的flask 变成了healthy
docker compose health示例
除了在Dockerfile中配置,还可以在docker-compose.yml文件中配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 version: "3.8" services: flask: build: context: ../flask dockerfile: Dockerfile image: flask-demo:latest environment: - REDIS_HOST=redis-server 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: - frontend - backend redis-server: image: redis:latest 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 - ../log/nginx:/var/log/nginx networks: - frontend networks: backend: frontend:
一站式hadoop集群便于测试开发 通过 docker-compose 快速部署 Hadoop 集群详细教程 - 大数据老司机 - 博客园 (cnblogs.com)
包含了zookeeper 3.8.0,mysql5.7,hadoop3.3.5,hive3.1.3,spark 3.3.2,Flink1.17.0
1、下载 1 git clone https://gitee.com/hadoop-bigdata/docker-compose-hadoop.git
2、创建网络 1 2 3 4 5 # 创建,注意不能使用hadoop_network,要不然启动hs2服务的时候会有问题!!! docker network create hadoop-network # 查看 docker network ls
3、部署 mysql5.7 1 2 3 4 5 6 7 8 cd docker-compose-hadoop/mysql docker-compose -f mysql-compose.yaml up -d docker-compose -f mysql-compose.yaml ps # root 密码:123456,以下是登录命令,注意一般在公司不能直接在命令行明文输入密码,要不然容易被安全抓,切记,切记!!! docker exec -it mysql mysql -uroot -p123456
4、部署 Hadoop Hive 1 2 3 4 5 6 7 8 9 10 11 12 13 14 cd docker-compose-hadoop/hadoop_hive docker-compose -f docker-compose.yaml up -d # 查看 docker-compose -f docker-compose.yaml ps # 查看指定列 docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}" # hive docker exec -it hive-hiveserver2 hive -e "show databases"; # hiveserver2 docker exec -it hive-hiveserver2 beeline -u jdbc:hive2://hive-hiveserver2:10000 -n hadoop -e "show databases;"
5.测试访问hdfs UI 和 yarn UI ,该项目设置的是http: //ip:30070 和http: //ip:30889
6.yarn ui地址在镜像中写死了,如果没有进行转发处理,宿主机无法访问,可以进入hadoop-yarn-nm进行修改,写自己的域名,docker-compose.yaml中的暴露端口与之对应,然后重启。
1 2 3 4 5 <property > <name > yarn.web-proxy.address</name > <value > container1:9111</value > </property >
8.Docker Swarm docker swarm 介绍 为什么不建议在生产环境中使用docker-compose
多机器如何管理?
如果跨机器做scale横向扩展?
容器失败退出时如何新建容器确保服务正常运行?
如何确保零宕机时间?
如何管理密码,Key等敏感数据?
其它
容器编排 swarm
Swarm的基本架构
Swarm 单节点快速上手 初始化
这个命令可以查看我们的docker engine有没有激活swarm模式, 默认是没有的,我们会看到
激活swarm,有两个方法:
初始化一个swarm集群,自己成为manager
加入一个已经存在的swarm集群
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Swarm Commands: config Manage Swarm configs node Manage Swarm nodes secret Manage Swarm secrets service Manage Swarm services stack Manage Swarm stacks swarm Manage Swarm Usage: docker swarm COMMAND Manage Swarm Commands: ca Display and rotate the root CA init Initialize a swarm join Join a swarm as a node and/or manager join-token Manage join tokens leave Leave the swarm unlock Unlock swarm unlock-key Manage the unlock key update Update the swarm
1 2 3 4 5 6 # 初始化一个集群 docker swarm init # 查看集群节点 docker node ls # 查看node的信息 docker node inspect <node id>
docker swarm init 背后发生了什么
主要是PKI和安全相关的自动化
创建swarm集群的根证书
manager节点的证书
其它节点加入集群需要的tokens
创建Raft数据库用于存储证书,配置,密码等数据
RAFT相关资料
看动画学会 Raft 算法
https://mp.weixin.qq.com/s/p8qBcIhM04REuQ-uG4gnbw
1 2 3 4 5 swarm join-token :可以查看或更换join token。 docker swarm join-token worker:查看加入woker的命令。 docker swarm join-token manager:查看加入manager的命令 docker swarm join-token --rotate worker:重置woker的Token。 docker swarm join-token -q worker:仅打印Token。
1 2 # 打开另外的虚拟机作为worker 加入,执行以下命令 docker swarm join --token SWMTKN-1-3hocsrayh278lv0lz17g0ty5bxedekofilq3pbiaj9xskqcjmh-331n58myelyc6bej36wosd69v 192.168.8.10:2377
1 2 # 打开manager所在的虚拟机,查看node情况 docker node ls
1 2 3 # 创建服务 docker service create --name nginx -d --replicas 3 nginx:stable-alpine docker service ps nginx
调节service的副本数在docker swarm中可以在创建service时,通过设置–replicas 来设置副本数,但是当服务运行起来后应该如何处理呢?docker 提供了scale命令来实现这个功能
1 2 # scale 动态扩缩容 docker service scale nginx=2
1 2 3 4 # 动态更新服务 docker service update --image nginx:latest nginx # 动态回滚服务 docker service update --rollback nginx
Docker Stack docker 与docker-compose的区别是后者可以部署多个容器;
docker swarm与docker stack的区别是stack可以部署多个服务;
docker stack与docker-compose的区别,stack是集群式,compose是单机模式,多用于开发测试。
stack获取不到.env,找到githup上的一种方法:
docker stack deploy in 1.13 doesn’t load .env file as docker-compose up does · Issue #29133 · moby/moby (github.com)
1 env $(cat .env | grep ^[A-Z] | xargs) docker stack deploy -c stack.yaml STACK
任务的编排可以进阶学习k8s。
9.Docker整合gitlab CICD 单模块下部署 Dockerfile配置
1 2 3 4 5 6 FROM openjdk:8 -jre-alpineVOLUME /tmp COPY ./target/cicd-0.0.1-SNAPSHOT.jar app.jar ENTRYPOINT ["java" ,"-Djava.security.egd=file:/dev/./urandom" ,"-jar" ,"/app.jar" ]
.gitlab-ci.yml配置
单环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 image: docker:latest variables: DOCKER_DRIVER: overlay2 DOCKER_HOST: tcp://192.168.8.10:2375 DOCKER_TLS_CERTDIR: '' TAG: root/hello-spring:v0.1 cache: key: m2-repo paths: - .m2/repository services: - docker:dind stages: - package - deploy maven-package: image: maven:3.5.0-jdk-8 tags: - maven stage: package script: - mvn clean package -Dmaven.test.skip=true artifacts: paths: - target/*.jar build-master: tags: - docker stage: deploy script: - docker build -t $TAG . - docker rm -f test || true - docker run -d --name test -p 8888 :8888 $TAG only: - master
多环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 # 1:需要用到的镜像 # 2:必须配置的一些环境变量。如果本地可不配置 DOCKER_HOST。 # 3:配置缓存,配置后,maven 下载的依赖可以被缓存起来,下次不需要重复去下载了。 # 4:配置需要用到的额外的服务。docker:dind,这个貌似是用于在 docker 中运行 docker 的一种东西,在项目的构建中需要。 # 有时需要在容器内执行 docker 命令,比如:在 jenkins 容器内运行 docker 命令执行构建镜像 # 直接在 docker 容器内嵌套安装 docker 未免太过臃肿 # 更好的办法是:容器内仅部署 docker 命令行工具(作为客户端),实际执行交由宿主机内的 docker-engine(服务器) # 先启动一个docker:dind容器A,再启动一个docker容器B,容器B指定host为A容器内的docker daemon。 # 5:stages,这是 Gitlab CI 中的概念,Stages 表示构建阶段,就是一些按序执行的流程,具体执行是依赖于 Jobs 的。 # 6 :定义的 Jobs 之一,用于构建 jar 包。内部又引入 maven 镜像来处理,负责执行 package 这一流程。script 为具体执行的脚本。 # 7:定义的 Jobs 之一,用于构建 Docker 镜像。负责执行 deploy 这一流程。具体执行 build 和 run。only 节点表示只监控 master 分支。 image: docker:latest #1 variables: #2 DOCKER_DRIVER: overlay2 DOCKER_HOST: tcp://192.168.8.10:2375 # docker host,本地可不写 DOCKER_TLS_CERTDIR: '' USERSERVICE_TAG: ':1.0' # 镜像版本号 USERSERVICE_NAME: cicd #镜像名称 USERSERVICE_RPORT: 8888 #镜像端口 容器内的端口与其不一致 USERSERVICE_DIRECTORY: cicd #模块目录 PROFILE_ACTIVE: prd cache: #3 key: m2-repo paths: - .m2/repository services: #4 - docker:dind stages: #5 - package - deploy-dev - deploy-prd maven-package: #6 image: maven:3.5.0-jdk-8 tags: - maven stage: package script: - mvn clean package -P $PROFILE_ACTIVE -Dmaven.test.skip=true artifacts: paths: - target/*.jar build-dev: tags: - docker stage: deploy-dev script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $USERSERVICE_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG || true - docker build -f Dockerfile -t $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG . - docker push $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker run -d --name $USERSERVICE_NAME -v $USERSERVICE_NAME:/apps --privileged=true -p $USERSERVICE_RPORT:$USERSERVICE_RPORT $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker image prune -f only: #限制作业在什么时候创建,定义了哪些分支或标签(branches and tags)的作业会运行 refs: #分支 - dev #changes: # 下面的文件中任一文件发生改变 # - .gitlab-ci.yml build-prd: #7 tags: - docker stage: deploy-prd before_script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rmi -f $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG || true - docker build -f Dockerfile -t $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG . - docker push $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker image prune -f - 'which ssh-agent || ( yum update -y && yum install openssh-client git -y )' - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh - ssh-keyscan 192.168.8.103 >> ~/.ssh/known_hosts #ssh-keyscan 从服务器收集 SSH公钥。 ssh-keyscan 是一个收集多个主机的 SSH 公共密钥的实用工具。它被设计用来帮助构建和验证 ssh_known_hosts 文件 - chmod 644 ~/.ssh/known_hosts script: # -tq 打印台静默不打印 - ssh -tq 192.168.8.103 << EOF - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $USERSERVICE_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG || true - docker run -d --name $USERSERVICE_NAME -v $USERSERVICE_NAME:/apps --privileged=true -p $USERSERVICE_RPORT:$USERSERVICE_RPORT $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker image prune -f - exit - EOF only: variables: [ $PROFILE_ACTIVE == "prd" ] refs: - master
多模块合并部署 根目录.gitlab-ci.yml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 variables: DOCKER_DRIVER: overlay2 DOCKER_HOST: tcp://192.168.8.10:2375 DOCKER_TLS_CERTDIR: '' PROFILE_ACTIVE: prd GIT_STRATEGY: clone stages: - service_first-package - service_first-dev-deploy - service_first-prd-deploy - service_second-package - service_second-dev-deploy - service_second-prd-deploy include: - local: service_first/.gitlab-ci.yml - local: service_second/.gitlab-ci.yml
子模块一目录Dockerfile配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 FROM openjdk:8 -jre-alpineLABEL author="gordon" RUN echo "Asia/Shanghai" > /etc/timezone ENV MYPATH /apps/service_first/RUN mkdir -p ${MYPATH} WORKDIR ${MYPATH} COPY service_first/target/*.jar app.jar ENTRYPOINT ["java" ,"-Djava.security.egd=file:/dev/./urandom" ,"-jar" ,"app.jar" ]
子模块一目录.gitlab-ci.yml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 image: docker:latest variables: FIRST_TAG: ':1.0' FIRST_NAME: service_first FIRST_RPORT: 8888 FIRST_DIRECTORY: service_first cache: key: m2-repo paths: - .m2/repository services: - docker:dind stages: - service_first-package - service_first-dev-deploy - service_first-prd-deploy maven-package:service_first: image: maven:3.5.0-jdk-8 tags: - maven stage: service_first-package script: - mvn clean package -P $PROFILE_ACTIVE -Dmaven.test.skip=true -pl $FIRST_DIRECTORY -am artifacts: paths: - $FIRST_DIRECTORY/target/*.jar build-dev:service_first: tags: - docker stage: service_first-dev-deploy script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $FIRST_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG || true - docker build -f $FIRST_DIRECTORY/Dockerfile -t $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG . - docker push $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG - docker run -d --name $FIRST_NAME -v /apps/$FIRST_DIRECTORY:/apps/$FIRST_DIRECTORY/log --privileged=true -p $FIRST_RPORT:$FIRST_RPORT $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG - docker image prune -f only: refs: - dev build-prd:service_first: tags: - docker stage: service_first-prd-deploy before_script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rmi -f $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG || true - docker build -f $FIRST_DIRECTORY/Dockerfile -t $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG . - docker push $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG - docker image prune -f - 'which ssh-agent || ( yum update -y && yum install openssh-client git -y )' - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh - ssh-keyscan 192.168 .8 .103 >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts script: - ssh -tq 192.168 .8 .103 << EOF - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $FIRST_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG || true - docker run -d --name $FIRST_NAME -v /apps/$FIRST_DIRECTORY:/apps/$FIRST_DIRECTORY/log --privileged=true -p $FIRST_RPORT:$FIRST_RPORT $CI_REGISTRY_IMAGE/$FIRST_NAME-$PROFILE_ACTIVE$FIRST_TAG - docker image prune -f - exit - EOF only: variables: [ $PROFILE_ACTIVE == "prd" ] refs: - main
子模块二目录Dockerfile配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 FROM openjdk:8 -jre-alpineLABEL author="gordon" RUN echo "Asia/Shanghai" > /etc/timezone ENV MYPATH /apps/service_second/RUN mkdir -p ${MYPATH} WORKDIR ${MYPATH} COPY service_second/target/*.jar app.jar ENTRYPOINT ["java" ,"-Djava.security.egd=file:/dev/./urandom" ,"-jar" ,"app.jar" ]
子模块二目录.gitlab-ci.yml配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 image: docker:latest variables: SECOND_TAG: ':1.0' SECOND_NAME: service_second SECOND_RPORT: 9999 SECOND_DIRECTORY: service_second cache: key: m2-repo paths: - .m2/repository services: - docker:dind stages: - service_second-package - service_second-dev-deploy - service_second-prd-deploy maven-package:service_second: image: maven:3.5.0-jdk-8 tags: - maven stage: service_second-package script: - mvn clean package -P $PROFILE_ACTIVE -Dmaven.test.skip=true -pl $SECOND_DIRECTORY -am artifacts: paths: - $SECOND_DIRECTORY/target/*.jar build-dev:service_second: tags: - docker stage: service_second-dev-deploy script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $SECOND_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG || true - docker build -f $SECOND_DIRECTORY/Dockerfile -t $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG . - docker push $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG - docker run -d --name $SECOND_NAME -v /apps/$SECOND_DIRECTORY:/apps/$SECOND_DIRECTORY/log --privileged=true -p $SECOND_RPORT:$SECOND_RPORT $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG - docker image prune -f only: refs: - dev build-prd:service_second: tags: - docker stage: service_second-prd-deploy before_script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rmi -f $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG || true - docker build -f $SECOND_DIRECTORY/Dockerfile -t $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG . - docker push $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG - docker image prune -f - 'which ssh-agent || ( yum update -y && yum install openssh-client git -y )' - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh - ssh-keyscan 192.168 .8 .103 >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts script: - ssh -tq 192.168 .8 .103 << EOF - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $SECOND_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG || true - docker run -d --name $SECOND_NAME -v /apps/$SECOND_DIRECTORY:/apps/$SECOND_DIRECTORY/log --privileged=true -p $SECOND_RPORT:$SECOND_RPORT $CI_REGISTRY_IMAGE/$SECOND_NAME-$PROFILE_ACTIVE$SECOND_TAG - docker image prune -f - exit - EOF only: variables: [ $PROFILE_ACTIVE == "prd" ] refs: - main
多模块
部署过程中遇到的问题
1 2 # 重新加载daemon systemctl daemon-reload
下载的镜像重复拉取
The image pull policy: never, if-not-present or always (default).
never是从不从远端拉镜像,只用本地。if-not-present 是优先本地,然后是从网络拉取镜像。always 是从远端拉取镜像。
1 2 3 解决办法:配置先从本地拉取。 vi /etc/gitlab-runner/config.toml 添加: pull_policy = "if-not-present"
maven下载慢
maven安装到宿主机,在runner里面进行挂载,这样可以重复使用,也可以使用国内的镜像源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 [[runners]] name = "Docker Maven Runner" url = "http://192.168.8.10:2280" token = "mPciztaPrpZsYSsWuRAX" executor = "docker" [runners.custom_build_dir] [runners.cache] [runners.cache.s3] [runners.cache.gcs] [runners.cache.azure] [runners.docker] tls_verify = false image = "maven:3.5.0-jdk-8" privileged = true pull_policy = "if-not-present" disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache", "/export/server/bigdata-stack/maven/apache-maven-3.9.2:/root/.m2"] shm_size = 0 [[runners]] name = "Docker Build Runner" url = "http://192.168.8.10:2280" token = "pRuA3NL6tsJsBgq-Ps65" executor = "docker" [runners.custom_build_dir] [runners.cache] [runners.cache.s3] [runners.cache.gcs] [runners.cache.azure] [runners.docker] tls_verify = false image = "docker:latest" privileged = true pull_policy = "if-not-present" disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache", "/export/server/bigdata-stack/maven/apache-maven-3.9.2:/root/.m2"] shm_size = 0
10.Docker 整合Sonar进行代码质量检查,统计单元测试覆盖率 单模块的.yml配置修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 image: docker:latest variables: DOCKER_DRIVER: overlay2 DOCKER_HOST: tcp://192.168.8.10:2375 DOCKER_TLS_CERTDIR: '' USERSERVICE_TAG: ':1.0' USERSERVICE_NAME: cicd USERSERVICE_RPORT: 6666 USERSERVICE_DIRECTORY: cicd PROFILE_ACTIVE: prd SCANNER_HOME: "/export/server/sonar-scanner" SCAN_DIR: "src" cache: key: m2-repo paths: - .m2/repository services: - docker:dind stages: - package - sonarqube-check - deploy-dev - deploy-prd maven-package: image: maven:3.5.0-jdk-8 tags: - maven stage: package script: - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install dependency:copy-dependencies -Dmaven.test.failure.ignore=true package -P $PROFILE_ACTIVE artifacts: expire_in: 1 days paths: - target/ sonarqube-check: image: name: sonarsource/sonar-scanner-cli:latest entrypoint: ["" ] tags: - sonar stage: sonarqube-check needs: [maven-package ] dependencies: - maven-package variables: SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" GIT_DEPTH: "0" cache: key: "${CI_JOB_NAME}" paths: - .sonar/cache script: - sonar-scanner -Dsonar.projectKey=sonarqube-check -Dsonar.sources=src/main/ -Dsonar.tests=src/test/ -Dsonar.coverage.exclusions=src/main/java/com/gordon/cicd/CicdApplication.java -Dsonar.scm.disabled=true -Dsonar.language=java -Dsonar.sourceEncoding=UTF-8 -Dsonar.host.url=http://192.168.8.10:9000 -Dsonar.login=b95cb41cb57230c28a79a177a9f0fc3bddf02a79 -Dsonar.java.binaries=target/classes -Dsonar.java.test.binaries=target/test-classes -Dsonar.java.libraries=target/dependency -Dsonar.java.test.libraries=target/dependency -Dsonar.java.surefire.report=target/surefire-reports -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml - awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print 100 *covered/instructions, "% covered" }' target/site/jacoco/jacoco.csv allow_failure: false only: - merge_requests - master - develop artifacts: paths: - target/ build-dev: tags: - docker stage: deploy-dev needs: [sonarqube-check ] script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $USERSERVICE_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG || true - docker build -f Dockerfile -t $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG . - docker push $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker run -d --name $USERSERVICE_NAME -v $USERSERVICE_NAME:/apps --privileged=true -p $USERSERVICE_RPORT:$USERSERVICE_RPORT $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker image prune -f only: refs: - dev build-prd: tags: - docker stage: deploy-prd needs: [sonarqube-check ] before_script: - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rmi -f $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG || true - docker build -f Dockerfile -t $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG . - docker push $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker image prune -f - 'which ssh-agent || ( yum update -y && yum install openssh-client git -y )' - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh - ssh-keyscan 192.168 .8 .103 >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts script: - ssh -tq 192.168 .8 .103 << EOF - docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD - docker rm -f $USERSERVICE_NAME || true - docker rmi -f $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG || true - docker run -d --name $USERSERVICE_NAME -v $USERSERVICE_NAME:/apps --privileged=true -p $USERSERVICE_RPORT:$USERSERVICE_RPORT $CI_REGISTRY_IMAGE/$USERSERVICE_NAME-$PROFILE_ACTIVE$USERSERVICE_TAG - docker image prune -f - exit - EOF only: variables: [ $PROFILE_ACTIVE == "prd" ] refs: - master
为了readme能查看结果,pom中jacoco做了exclude
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.0.1.RELEASE</version > <relativePath /> </parent > <groupId > com.gordon</groupId > <artifactId > cicd</artifactId > <version > 0.0.1-SNAPSHOT</version > <packaging > jar</packaging > <name > cicd</name > <description > Demo project for Spring Boot</description > <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <project.reporting.outputEncoding > UTF-8</project.reporting.outputEncoding > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <build > <pluginManagement > <plugins > <plugin > <groupId > org.sonarsource.scanner.maven</groupId > <artifactId > sonar-maven-plugin</artifactId > <version > 3.7.0.1746</version > </plugin > </plugins > </pluginManagement > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.1</version > <configuration > <source > ${java.version}</source > <target > ${java.version}</target > </configuration > </plugin > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-surefire-plugin</artifactId > <version > 2.21.0</version > <configuration > <testFailureIgnore > true</testFailureIgnore > <argLine > @{argLine} -Dfile.encoding=utf-8</argLine > </configuration > </plugin > <plugin > <groupId > org.jacoco</groupId > <artifactId > jacoco-maven-plugin</artifactId > <version > 0.8.7</version > <configuration > <excludes > <exclude > **/CicdApplication.class</exclude > </excludes > </configuration > <executions > <execution > <id > prepare-agent</id > <goals > <goal > prepare-agent</goal > </goals > </execution > <execution > <id > report</id > <goals > <goal > report</goal > </goals > </execution > </executions > </plugin > </plugins > </build > </project >
覆盖率展示格式化
多模块的子模块配置更新如下:
这里提供一种思路,新建一个模块把其他模块作为依赖引入,然后计算总的覆盖率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | .gitlab-ci.yml | pom.xml | README.md +---report-aggregate | .gitlab-ci.yml | pom.xml | +---service_first | | .gitlab-ci.yml | | Dockerfile | | pom.xml | | | \---src | +---main | | +---java | | | \---com | | | \---gordon | | | | FirstApplication.java | | | | | | | \---controller | | | TestController.java | | | | | \---resources | | application.properties | | | \---test | \---java | \---com | \---gordon | \---controller | TestControllerTest.java | \---service_second | .gitlab-ci.yml | Dockerfile | pom.xml | \---src +---main | +---java | | \---com | | \---gordon | | | SecondApplication.java | | | | | \---controller | | TestController.java | | | \---resources | application.properties | \---test \---java \---com \---gordon \---controller TestControllerTest.java
创建第三模块
pom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > cicd_muil</artifactId > <groupId > com.gordon</groupId > <version > 1.0-SNAPSHOT</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > report-aggregate</artifactId > <version > 1.0-SNAPSHOT</version > <dependencies > <dependency > <groupId > com.gordon</groupId > <artifactId > service_first</artifactId > <version > 1.0-SNAPSHOT</version > </dependency > <dependency > <groupId > com.gordon</groupId > <artifactId > service_second</artifactId > <version > 1.0-SNAPSHOT</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.jacoco</groupId > <artifactId > jacoco-maven-plugin</artifactId > <version > 0.8.7</version > <configuration > <excludes > <exclude > **/FirstApplication.class</exclude > <exclude > **/SecondApplication.class</exclude > </excludes > </configuration > <executions > <execution > <id > jacoco-report-aggregate</id > <phase > install</phase > <goals > <goal > report-aggregate</goal > </goals > </execution > </executions > </plugin > </plugins > </build > </project >
.gitlab-ci.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 image: docker:latest variables: REPORT_NAME: report-aggregate REPORT_DIRECTORY: report-aggregate cache: key: m2-repo paths: - .m2/repository services: - docker:dind stages: - report-aggregate maven-package:report-aggregate: image: maven:3.5.0-jdk-8 tags: - maven stage: report-aggregate script: - mvn clean install dependency:copy-dependencies -Dmaven.test.failure.ignore=true -pl $REPORT_DIRECTORY -am - awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print 100 *covered/instructions, "% covered" }' $REPORT_DIRECTORY/target/site/jacoco-aggregate/jacoco.csv artifacts: paths: - $REPORT_DIRECTORY/target/
查看pipeline运行情况以及扫描结果
test