docker¶
docker 基本原理¶
docker是一个开源的应用容器引擎,开发者可以利用 docker 打包自己的应用和依赖包到一个可移植的容器中,发布到各linux发行版中,这个也一种虚拟化,但是请注意不同于 vmware kvm xen 那种虚拟化。
docker容器中运行的程序在宿主机中是可以访问的: ps
docker容器工作目录在宿主机同样可以访问:df
为什么要用docker¶
微服务: docker 提供了轻量,简单的建模方式。一个应用一个容器,减少耦合度,docker官方也是推荐一个容器一个应用,这个场景下服务器中的分布式应用就相当于一群互联的容器。
启动速度快,支持快速部署,版本回滚,滚动升级。
保持环境一致
CI
与 VM 的区别¶

docker基础¶

docker核心组件
docker客户端与docker服务,这两个在一起就是docker引擎:docker客户端可以是cli ,可以是http restful client , docker服务就是docker daemon
docker镜像:
docker 镜像由是文件系统叠加而来的, 最底层是 bootfs(启动之后会被卸载)
rootfs 可以提供多种操作系统( readonly )

registry: 类似于 git 仓库, 它包括了 镜像、层以及 镜像的 metadata
docker 容器
docker部署¶
运行一个 docker¶
安装完 docker 并且启动 docker 守护进程后通过
docker info
查看 docker daemon 服务端信息创建一个容器
root@photon:~ # docker run -it --name test_docker ubuntu /bin/bash
########################################################
# -i 表示容器内的 STDIN 是开启的
# -t 表示为容器分配一个伪 tty
# ubuntu 表示容器的 image 名字(如果不指定 tag ,默认为 latest)
# /bin/bash 表示在容器内执行的命令
########################################################
root@6706395e88a3:/# cat /etc/*release*
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
root@6706395e88a3:/#
########################################################
# docker 已经在 hosts 文件中为该容器的 IP 添加 hosts 记录
########################################################
root@896840bdedf6:/# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.3 896840bdedf6
root@896840bdedf6:/#
docker 常用命令¶
docker pull/push : 获取/推送 image
docker search: 搜索 image ( 从 docker-hub 中搜索)
docker rm/rmi : 删除容器/镜像
docker images : 列出 images
docker start/stop/restart
docker ps: list containers
docker logs [ -f ]: Fetch the logs of a container
docker top / docker stats:
docker exec: Run a command in a running container
docker inspect: Return low-level information on Docker objects
docker save: 保存 image 到文件
docker save busybox:latest > docker-image-bb.tar
docker load: 从文件导入 image
docker load < docker-image-bb.tar
构建 docker¶
docker commit¶
可以理解为 往版本控制系统内提交变更。 其中 commit 提交知识创建容器的镜像与当前容器之间的差异部分。
步骤
创建一个容器
在容器内变更( 比如使用 ubuntu:latest image 创建出来的容器,缺少常用测试工具,我们通过 apt 在 容器内安装应用)
通过 docker commit 把此容器提交为 image
docker run -it ubuntu --name test-commit /bin/bash
##
apt update
apt install iputils-ping
exit
##
docker commit test-commit ubuntu-ping
docker images
docker build with a Dockerfile(推荐)¶
Dockerfile 文件与 docker build 构建镜像: Dockerfile 使用基于 DSL 语法构建一个 Docker 镜像,推荐使用 Dockerfile 方式的原因是:通过 Dockerfile 方式更具备 可重复性、幂等性。
步骤
创建目录( build env)
创建 Dockerfile
docker build
详细说明一下 Dockerfile:
Version: 0.0.1
FROM ubuntu:16.04
MAINTAINER abc@gmail.com
RUN apt-get update; apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
>/var/www/html/index.html
EXPOSE 80
Dockerfile 由一组指令和参数构成,每条指令必须大些( FROM ),每一条指令都会创建一个新的分层并且提交。
workflow:
• Docker runs a container from the image.
• An instruction executes and makes a change to the container.
• Docker runs the equivalent of docker commit to commit a new layer.
• Docker then runs a new container from this new image.
• The next instruction in the file is executed, and the process repeats until all instructions have been executed.
Dockerfile 指令
CMD:指定一个容器启动时要运行的命令( RUN 指令时构建时运行的命令)如果在 Dockerfile 中有多条 CMD 指令,则最后一条生效(如果需要在一个容器中运行多个进程或者命令,可以使用 Supervisor/runit 之类的进程管理工具),可以被 docker run 后的参数覆盖
小技巧
如果需要在一个容器中运行多个进程或者命令,可以使用Supervisor/runit 之类的进程管理工具
ENTRYPOINT:与 CMD 指令类似,在 docker run 后面的参数会被当作参数传递给 ENTRYPOINT。
小技巧
docker run --entrypoint="" 参数可以覆盖 ENTRYPOINT 指令
WORKDIR:从 image 创建一个新的容器时,在容器内部设置一个工作目录, ENTRYPOINT 和 CMD 指定的程序会在这个目录下执行。
小技巧
docker run -w 参数可以覆盖工作目录
ENV: image 构建过程设置环境变量,后续的 RUN 指令会使用 ENV。 ENV 一条指令设置多个环境变量
ENV PYTHON_PATH=/usr/local/bin/python3 ARCHFLAGS="-arch i386"
, 也可以在其他的指令里面使用ENV TARGET_DIR /opt/app WORKDIR $TARGET_DIR
,
小技巧
docker run -e 参数可以传递环境变量
USER:基于该 image 启动的容器以 USER 用户身份来运行,也可以指定 UID以及 GID, 如不指定用户,默认用户为 root,docker run -u覆盖该值
USER nginx # 组合 USER user USER user:group USER uid USER uid:gid USER user:gid USER uid:group
VOLUME: 用来向基于 image 创建的容器添加卷。一个卷可以是存在与一个或者多个容器内的特定目录,这个目录可以绕过联合文件系统,为容器提供数据持久化。添加多个卷
VOLUME ["/opt/project", "/data"]
,Dockerfile 中的 VOLUME 与 docker run -v /data /data 有一个区别是,通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。
The VOLUME instruction adds volumes to any container created from the image. A volume is a specially designated directory within one or more containers that bypasses the Union File System to provide several useful features for persistent or shared data:
Volumes can be shared and reused between containers.
A container doesn’t have to be running to share its volumes.
Changes to a volume are made directly.
Changes to a volume will not be included when you update an image. • Volumes persist until no containers use them.
ADD:将构建环境下的文件和目录复制到镜像中
ADD app.py /home/data/web/app.py
,Docker 以目的地址的字符判断文件源是目录还是文件。如果目的位置不存在,Docker 会创建这个全路径,包括路径中的目录,新创建的目录与文件属性都为0755 UID:0 GID:0。
注意
另外 ADD 有点特别,就是在特殊情况下对 压缩包 解压缩
COPY :与 ADD 类似,但是不会做 extra 操作
LABEL: 向 image 中添加 kv 形式元数据,
LABEL version="1.0"
STOPSIGNAL: 设置停止容器时发送什么样的系统调用信号给容器。如 9( SIGKILL )
SHELL:
SHELL ["executable", "parameters"]
,这里我们看一下默认值即可:
Linux:
["/bin/sh", "-c"]
Windows is
["cmd", "/S", "/C"]
针对 windows 比较有效( cmd/ps )
HEALTHCHECK:
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1 --interval=DURATION (default: 30s) --timeout=DURATION (default: 30s) --start-period=DURATION (default: 0s) --retries=N (default: 3) # return code: # 0: healthy - 容器健康, 可以使用; # 1: unhealthy - 容器工不正常, 需要诊断; # 2: reserved -保留, 不要使用这个返回值;
EXPOSE: 为构建的镜像设置监听端口,使容器在运行时监听。
注意
默认为 TCP,如果协议为 UDP:EXPOSE 80/udp
RUN: RUN 指令将在当前 image 顶部的 new layer 中执行命令,并提交, 生成的提交 image。两种形式:
RUN <command>
RUN ["executable", "param1", "param2" ....]
资源限制¶
默认情况下,容器没有资源限制,并且可以使用主机内核调度程序允许的尽可能多的给定资源。
CPU:
CPU affinity:CPUs in which to allow execution (0-3, 0,1)
docker run --cpuset-cpus="0" ubuntu-ping /bin/bash
docker run --cpuset-cpus="0,1" ubuntu-ping /bin/bash
docker run --cpuset-cpus="0-2" ubuntu-ping /bin/bash
CPU 资源相对限制
CPU 资源绝对限制
Memory:
使用多个容器¶
docker link¶
docker run -d --name redis redis
docker run -d -p 5000:5000 --link redis -v /home/web [image:tag] python app.py
docker compose¶
docker-compose 使用场景¶
Start, stop, and rebuild services
View the status of running services
Stream the log output of running services
Run a one-off command on a service
安装¶
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 也可以通过 pip 安装 pip install docker-compose
chmod +x /usr/local/bin/docker-compose
创建步骤
Define your app’s environment with a
Dockerfile
so it can be reproduced anywhere.Define the services that make up your app in
docker-compose.yml
so they can be run together in an isolated environment.Run
docker-compose up
and Compose starts and runs your entire app.
例子¶
docker-compose.yml
version: '3'
services:
web:
image: reg.test.k8s/cicd-test/flask-compose:0.1
command: python app.py
ports:
- "5000:5000"
volumes:
- .:/home/web
redis:
image: reg.test.k8s/library/redis
Dockerfile
FROM reg.test.k8s/cicd-test/flask-base:0.2
ADD . /home/web
WORKDIR /home/web
RUN pip install -r requirements.txt
CI/CD¶
引言¶
在传统软件开发过程中,集成通常发生在每个人都完成了各自的工作之后。在项目尾声阶段,通常集成还要花费很长的时间来完成。 持续集成是一个将集成提前至开发周期的早期阶段的实践方式,让构建、测试和集成代码更经常与快速的发生。 持续交付让软件在短周期可以被可靠地发布。其目的在于更快、更频繁地构建、测试以及发布软件。通过加强对生产环境的应用进行渐进式更新。
系统整体设计¶
![digraph G {
graph [rankdir=LR ]
subgraph cluster0 {
style=filled;
color=lightgrey;
node [style=filled,color=white];
Harbor[shape=cylinder];
test_master01 -> test_node01[ fontcolor = "black", label = "rollupdate" width = 3];
test_master01 -> test_node02[ fontcolor = "black", label = "rollupdate"];
test_master01 -> test_node03[ fontcolor = "black", label = "rollupdate"];
label = "k8s-test-env";
}
subgraph cluster1 {
style=filled;
color=lightgrey;
node [style=filled,color=white];
ECR[shape=cylinder];
aws_master01 -> aws_node01[ fontcolor = "black", label = "rollupdate"];
aws_master01 -> aws_node02[ fontcolor = "black", label = "rollupdate"];
aws_master01 -> aws_node03[ fontcolor = "black", label = "rollupdate"];
label = "k8s-aws_env";
}
Jenkins[shape=tab];
GithubLocal[shape=cylinder];
subgraph cluster2 {
node [style=filled];
DEV_A;
DEV_B;
DEV_C;
label = "Git";
color=blue
}
DEV_C -> GithubLocal[ color = "blue"];
DEV_A -> GithubLocal[ color = "blue"];
DEV_B -> GithubLocal[color="blue", fontcolor = "blue", label = "git push"];
QA[shape=circle];
GithubLocal -> Jenkins[ fontcolor = "black", label = "webhook"];
QA -> Jenkins[ color="green";fontcolor = "green", label = "confirm test" ];
Jenkins -> Harbor[ color="blue", fontcolor = "blue" label= "push image" weight=10, penwidth=2 arrowsize=1];
Jenkins -> test_master01 [color="red";fontcolor = "red", label = "update"];
test_master01 -> Harbor[fontcolor = "black", label = "pull image" len=0000.1] ;
Jenkins -> ECR[ color="blue", fontcolor = "blue" label= "push image" weight=20];
Jenkins -> aws_master01[label = "update canary(1)"]
aws_master01 -> ECR[ label="pull image"]
QA -> Jenkins[ color="red";fontcolor = "red", label = "confirm staging"];
Jenkins -> aws_master01[label = "update all(2)"]
}](../_images/graphviz-883c7e603b40882b1fcb17027907fd9a8b27c1e6.png)
开发人员提交的到 Git 仓库中应包含以下文件:
应用代码
Jenkinsfile:Jenkins 的 pipelines 文件
Dockerfile: docker 根据 Dockerfile 文件构建 image
Helm value 文件: Kubernetes 的包管理工具,类似于 yum/rpm apt/dpkg
应用以 Docker image / helm chart 方式发布(开发人员需在提交代码的同时提交 Dockerfile Jenkinsfile 和 Helm 相关配置)
代码推送到 GitHub 仓库后会触发 CI webhook,Github 发送触发 CI 的 http request 到 Jenkins 服务器
Jenkins 服务器收到 Github 的 request,从指定的 repo 拉取代码并读取 Jenkinsfile 开始构建
Jenkins pipleline 应包含以下 stage:
获取 Git
从 harbor 中拉取 base image
根据 Dockerfile 构建 image
构建成功后 Jenkins 使用新构建的 image 启动容器执行 unittest
unittest 结束后将 image push 到 harbor
Jenkins 通过 Helm upgrade / set new image ( deployment ) 方式更新到测试环境的 Kubernetes,执行完毕这一步 Jenkins 进入暂停状态
kubernetes 的 nodes 滚动更新,完成应用升级,升级完毕后,QA 根据测试用例测试,如果结果符合预期,测试登录 Jenkins 恢复操作
Jenkins 把刚才构建的 image 推送到 aws ECR
Jenkins 通过 Helm upgrade / set new image ( deployment ) 方式更新到AWS 生产环境的 Kubernetes 的 灰度发布环境,执行完毕这一步 Jenkins 再一次进入暂停状态
kubernetes 的 nodes 滚动更新,完成应用升级,升级完毕后,QA 根据测试用例测试,如果结果符合预期,测试登录 Jenkins 恢复操作
Jenkins 通过 Helm upgrade / set new image ( deployment ) 方式更新到 AWS 生产环境的 Kubernetes 正式环境发布应用
生产环境应用更新完毕。
Jenkins 在构建过程中任意一步 shell 执行的 ``exit code !=0`` 认为本次构建失败。
灰度发布¶
通过 k8s 提供强大的 label selector ,我们可以实现灰度发布:
回退方案¶
k8s 集群提供了 undo 和 history 等方式 rollback:
undo 为直接 rollback 到上一个版本
kubectl rollout undo deployment/flask-deployment-canary
history 可以根据历史版本 rollback
helm 也提供类似功能:
helm rollback [flags] [RELEASE] [REVISION]
运行环境¶
软件¶
版本管理软件:Github
自动化构建工具:Jenkins
Kubernetes 集群:测试环境一套集群, AWS生产环境一套集群
docker image 镜像仓库: 测试环境 harbor, 生产环境是 ECR
自动化测试工具: 待补充
安全问题¶
Jenkins 需要有很多的权限:
git仓库访问权限,
对 K8S 集群可以更新 helm 或 set new image
Github webhook 的安全性待确认
staging 阶段 canary pod 会使用现网数据,可能会产生脏数据
优点¶
Dockerfile, Jenkinsfile, 以及 Helm 配置文件都存在 Git 仓库中,可以很方便 diff 版本之间的差异
提升集成与发布的效率
不足与改进¶
对 Jenkins 的依赖比较大,
部分服务存在单点问题( Jenkins, Harbor)