一、docker的介绍和安装
1.1、容器技术的介绍
注意我们这里所说的容器container是指的一种技术,而Docker只是一个容器技术的实现,或者说让容器技术普及开来的最成功的实现
1.1.1、容器正在引领基础架构的一场新的革命
-
90年代的PC
-
00年代的虚拟化
-
10年代的cloud
-
11年代的container
1.1.2、什么是container(容器)
容器是一种快速的打包技术
Package Software into Standardized Units for Development, Shipment and Deployment
-
标准化
-
轻量级
-
易移植
1.1.3、为什么容器技术会出现
容器技术出现之前:
容器技术出现之后:
容器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)。
1.1.4、容器的标准化(docker != container
)
在2015年,由Google,Docker、红帽等厂商联合发起了OCI(Open Container Initiative)组织,致力于容器技术的标准化。
1.1.5、容器运行时标准 (runtime spec)
简单来讲就是规定了容器的基本操作规范,比如如何下载镜像,创建容器,启动容器等。
1.1.6、容器镜像标准(image spec)
主要定义镜像的基本格式。
1.1.7、容器是关乎“速度”
-
容器会加速你的软件开发
-
容器会加速你的程序编译和构建
-
容器会加速你的测试
-
容器会速度你的部署
-
容器会加速你的更新
-
容器会速度你的故障恢复
1.1.8、容器的快速发展和普及
到2020年,全球超过50%的公司生产环境使用container - Gartner
1.1.9、 参考资料
- Containers vs Virtual Machines: Key Differences and Benefits
- https://en.wikipedia.org/wiki/Linux_namespaces
- https://en.wikipedia.org/wiki/Cgroups Linux Cgroup 入门教程:基本概念 · 云原生实验室
- Open Container Initiative - Open Container Initiative
- Docker Index Shows Continued Massive Developer Adoption and Activity to Build and Share Apps with Docker | Docker
1.2、docker架构图
1.3、在Windows系统上安装Docker
下载Docker Desktop for windows地址:Windows | Docker Docs
-
如果大家的windows10没有安装WSL2的话,请选择 Hyper-V backend and Windows containers
-
如果大家的Windows10安装了WSL2,那么请跳到
安装Windows10 WSL2环境和Docker
这一节
1.4、在Linux系统上安装Docker
安装脚本地址:https://get.docker.com
下载安装脚本
curl -fsSL get.docker.com -o get-docker.sh
运行脚本
sh get-docker.sh
启动docker
sudo systemctl start docker
查看docker安装版本
sudo docker version
1.5、在Mac系统上安装Docker
下载Docker Desktop for mac地址:Mac | Docker Docs
-
intel芯片,请选择 Mac with intel chip
-
M1芯片,请选择 Mac with Apple chip
1.6、安装Windows10 WASL2环境和Docker
Windows10 WSL2环境的搭建系列视频
- B站地址:麦兜搞IT的个人空间-麦兜搞IT个人主页-哔哩哔哩视频
- youtube地址:https://www.youtube.com/playlist?list=PLfQqWeOCIH4ACS0037k1KLNIv5f646jbr
1.7、Docker Machine 搭建 docker 环境
文档地址:Deprecated products and features | Docker Docs
1.8、通过 Vagrant 搭建 Linux Docker 环境
Vagrant是一个快速创建虚拟机的工具,可以参考youtube或者B站的Vagrant入门系列视频
- youtube地址:https://www.youtube.com/playlist?list=PLfQqWeOCIH4B6YAEXMr6cx4AfnKNBLbZO
- b站地址:麦兜搞IT的个人空间-麦兜搞IT个人主页-哔哩哔哩视频
1.9、本节常见问题
-
windows 10 家庭版能装 Hyper-v 么?
-
docker 在 windows 上启动失败,the docker client must be run with elevated privileges to connect
2、容器的快速上手
2.1、Docker CLI命令行介绍
docker version
docker命令行的基本使用
docker + 管理的对象(比如容器,镜像) + 具体操作(比如创建,启动,停止,删除)
例如:
-
docker image pull nginx
拉取一个叫nginx的docker image镜像 -
docker container stop web
停止一个叫web的docker container容器
2.2、Image vs Container 镜像 vs 容器
2.2.1、image镜像
-
Docker image是一个
read-only
文件 -
这个文件包含文件系统,源码,库文件,依赖,工具等一些运行application所需要的文件
-
可以理解成一个模板
-
docker image具有分层的概念
2.2.2、container容器
-
“一个运行中的docker image”
-
实质是复制image并在image最上层加上一层
read-write
的层 (称之为container layer
,容器层) -
基于同一个image可以创建多个container
2.2.3、docker image的获取
- 自己制作
- 从registry拉取(比如docker hub)
2.3、容器的基本操作
- --name="name" 容器名字:用来区分容器
- -d 后台方式运行:相当于nohup
- -it 使用交互式运行:进入容器查看内容
- -p 指定容器的端口(四种方式)小写字母p
- -p ip:主机端口:容器端口
- -p 主机端口:容器端口
- -p 容器端口
- 容器端口
- -P 随机指定端口(大写字母P)
- 查看容器日志:docker logs -tf --tail 容器id
- 查看容器中进程的信息:docker top 容器id
- 查看容器元数据:docker inspect 容器id
- 进入当前正在运行的容器:docker exec -it 容器id /bin/bash
- 从容器内拷贝文件到主机:docker cp 容器id:容器内路径 目的主机的路径
- 后台启动容器:docker run -d 镜像名
- 启动容器:docker start 容器id
- 重启容器:docker restart 容器id
- 停止当前正在运行的容器:docker stop 容器id
- 强制停止当前容器:docker kill 容器id
举例:
$ docker container run nginx
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
343fd4031609 nginx "/docker-entrypoint.…" 6 seconds ago Up 5 seconds 80/tcp xenodochial_clarke
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
343fd4031609 nginx "/docker-entrypoint.…" 14 seconds ago Up 13 seconds 80/tcp xenodochial_clarke
$ docker container stop 34
34
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
343fd4031609 nginx "/docker-entrypoint.…" 29 seconds ago Exited (0) 5 seconds ago xenodochial_clarke
d9095daa8bcf nginx "/docker-entrypoint.…" 28 minutes ago Exited (0) 28 minutes ago suspicious_shamir
$ docker container rm 34
34
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9095daa8bcf nginx "/docker-entrypoint.…" 28 minutes ago Exited (0) 28 minutes ago suspicious_shamir
$
2.4、docker container 命令小技巧
2.4.1、批量停止
$ 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
方法一:
$ docker container stop cd3 269 34b 751
方法二
$ 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
$
2.4.2、批量删除
docker container rm $(docker container ps -aq)
docker system prune -a -f
可以快速对系统进行清理,删除停止的容器,不用的image,等等。
2.5、Container Mode 容器运行的各种模式
2.5.1、attach 模式
范例指令: docker container run -p 80:80 nginx
-
透过这种方式创建容器的话,容器在前台执行
-
容器的输入输出结果会反映到本地端,本地端的输入输出也会反映到容器,例如能在终端机看到网页浏览器的 log,ctrl + c 会让容器停止执行
-
一般情况不推荐使用
2.5.2、detach 模式
范例指令: docker container run -d -p 80:80 nginx
-
容器会在后台执行
2.6、连接容器的 shell
2.6.1、创建一个容器并进入交互式模式
docker container run -it
举例:
➜ ~ 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
2.6.2、在一个已经运行的容器里执行一个额外的command
docker container exec -it
举例:
➜ ~ 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
#
# exit
➜ ~
2.7、容器和虚拟机 Container vs VM
2.7.1、容器不是Mini虚拟机
-
容器其实是进程Containers are just processes
-
容器中的进程被限制了对CPU内存等资源的访问
-
当进程停止后,容器就退出了
2.7.2、容器的进程process
以下是在Ubuntu20.04中演示,因Windows环境下的Docker和Linux具有一些差异。
pstree
命令需要额外安装,可以使用yum install psmisc
或者sudo apt-get install psmisc
安装
➜ ~ docker container run -d nginx
57fe4033dd7e1e620a0b6a7b83b85d4f8f98772f0ce585624c384de254826fd0
➜ ~ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
57fe4033dd7e nginx "/docker-entrypoint.…" 4 seconds ago Up 3 seconds 80/tcp festive_proskuriakova
➜ ~ docker container top 57f
UID PID PPID C STIME TTY TIME CMD
root 7646 7625 0 12:14 ? 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 7718 7646 0 12:14 ? 00:00:00 nginx: worker process
➜ ~
➜ ~
➜ ~ ps -aef --forest
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 May14 ? 00:00:00 /init
root 157 1 0 May14 ? 00:00:00 /init
root 523 157 0 May14 ? 00:02:32 \_ /usr/bin/dockerd -p /var/run/docker.pid
root 545 523 0 May14 ? 00:25:55 | \_ containerd --config /var/run/docker/containerd/containerd.toml --log-level info
root 7625 157 0 12:14 ? 00:00:00 \_ /usr/bin/containerd-shim-runc-v2 -namespace moby -id 57fe4033dd7e1e620a0b6a7b83b85d4f8f98772f0ce585624c384de254826fd0 -address /var/run/d
root 7646 7625 0 12:14 ? 00:00:00 \_ nginx: master process nginx -g daemon off;
systemd+ 7718 7646 0 12:14 ? 00:00:00 \_ nginx: worker process
root 6442 1 0 May18 ? 00:00:00 /init
root 6443 6442 0 May18 ? 00:00:00 \_ /init
penxiao 6444 6443 0 May18 pts/2 00:00:01 \_ -zsh
penxiao 7770 6444 0 12:15 pts/2 00:00:00 \_ ps -aef --forest
➜ ~
➜ ~ pstree -halps 7718
init,1
└─init,157
└─containerd-shim,7625 -namespace moby -id 57fe4033dd7e1e620a0b6a7b83b85d4f8f98772f0ce585624c384de254826fd0 -address /var/run/docker/containerd/containerd.sock
└─nginx,7646
└─nginx,7718
2.7.3、在Windows或MacOS上访问MobyLinux虚拟机
地址:Docker Tip #70: Gain Access to the MobyLinux VM on Windows or MacOS — Nick Janetakis
2.8、docker container run 背后发生了什么
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)
- -d :表示后台运行 ,-p:指定端口 服务器端口:docker容器端口 ,-n:容器名称(-n可省略)
三、镜像的创建、管理、和发布
3.1、镜像的获取
-
pull from
registry
(online) 从registry拉取-
public(公有)
-
private(私有)
-
-
build from
Dockerfile
(online) 从Dockerfile构建 -
load from
file
(offline) 文件导入 (离线)
3.2、镜像的基本操作
3.2.1、镜像的拉取
默认从Docker Hub拉取,如果不指定版本,会拉取最新版
$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
69692152171a: Pull complete
49f7d34d62c1: Pull complete
5f97dc5d71ab: Pull complete
cfcd0711b93a: Pull complete
be6172d7651b: Pull complete
de9813870342: Pull complete
Digest: sha256:df13abe416e37eb3db4722840dd479b00ba193ac6606e7902331dcea50f4f1f2
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
指定版本
$ docker pull nginx:1.20.0
1.20.0: Pulling from library/nginx
69692152171a: Already exists
965615a5cec8: Pull complete
b141b026b9ce: Pull complete
8d70dc384fb3: Pull complete
525e372d6dee: Pull complete
Digest: sha256:ea4560b87ff03479670d15df426f7d02e30cb6340dcd3004cdfc048d6a1d54b4
Status: Downloaded newer image for nginx:1.20.0
docker.io/library/nginx:1.20.0
从Quay上拉取镜像
$ docker pull quay.io/bitnami/nginx
Using default tag: latest
latest: Pulling from bitnami/nginx
2e6370f1e2d3: Pull complete
2d464c695e97: Pull complete
83eb3b1671f4: Pull complete
364c139450f9: Pull complete
dc453d5ae92e: Pull complete
09bd59934b83: Pull complete
8d2bd62eedfb: Pull complete
8ac060ae1ede: Pull complete
c7c9bc2f4f9d: Pull complete
6dd7098b85fa: Pull complete
894a70299d18: Pull complete
Digest: sha256:d143befa04e503472603190da62db157383797d281fb04e6a72c85b48e0b3239
Status: Downloaded newer image for quay.io/bitnami/nginx:latest
quay.io/bitnami/nginx:latest
3.2.2、镜像的查看
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
quay.io/bitnami/nginx latest 0922eabe1625 6 hours ago 89.3MB
nginx 1.20.0 7ab27dbbfbdf 10 days ago 133MB
nginx latest f0b8a9a54136 10 days ago 133MB
3.2.3、镜像的删除
$ docker image rm 0922eabe1625
Untagged: quay.io/bitnami/nginx:latest
Untagged: quay.io/bitnami/nginx@sha256:d143befa04e503472603190da62db157383797d281fb04e6a72c85b48e0b3239
Deleted: sha256:0922eabe16250e2f4711146e31b7aac0e547f52daa6cf01c9d00cf64d49c68c8
Deleted: sha256:5eee4ed0f6b242e2c6e4f4066c7aca26bf9b3b021b511b56a0dadd52610606bd
Deleted: sha256:472a75325eda417558f9100ff8b4a97f4a5e8586a14eb9c8fc12f944b26a21f8
Deleted: sha256:cdcb5872f8a64a0b5839711fcd2a87ba05795e5bf6a70ba9510b8066cdd25e76
Deleted: sha256:e0f1b7345a521469bbeb7ec53ef98227bd38c87efa19855c5ba0db0ac25c8e83
Deleted: sha256:11b9c2261cfc687fba8d300b83434854cc01e91a2f8b1c21dadd937e59290c99
Deleted: sha256:4819311ec2867ad82d017253500be1148fc335ad13b6c1eb6875154da582fcf2
Deleted: sha256:784480add553b8e8d5ee1bbd229ed8be92099e5fb61009ed7398b93d5705a560
Deleted: sha256:e0c520d1a43832d5d2b1028e3f57047f9d9f71078c0187f4bb05e6a6a572993d
Deleted: sha256:94d5b1d6c9e31de42ce58b8ce51eb6fb5292ec889a6d95763ad2905330b92762
Deleted: sha256:95deba55c490bbb8de44551d3e6a89704758c93ba8503a593cb7c07dfbae0058
Deleted: sha256:1ad1d903ef1def850cd44e2010b46542196e5f91e53317dbdb2c1eedfc2d770c
3.3、镜像的导出和导入(offline)
3.3.1、导出镜像
//docker image save 镜像名称 -o 保存为镜像文件名称
docker image save nginx:1.20.0 -o nginx.image
3.3.2、导入镜像
//docker image load -i 镜像文件名称
docker image load -i ./nginx.image
举例:
PS C:\Users\Peng Xiao\docker.tips\image> docker image ls
nginx 1.20.0 7ab27dbbfbdf 12 days ago 133MB
nginx latest f0b8a9a54136 12 days ago 133MB
PS C:\Users\Peng Xiao\docker.tips\image> docker image save nginx:1.20.0 -o nginx.image
PS C:\Users\Peng Xiao\docker.tips\image> ls
Directory: C:\Users\Peng Xiao\docker.tips\image
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 5/24/2021 1:40 PM 137379328 nginx.image
PS C:\Users\Peng Xiao\docker.tips\image> docker image rm 7ab
Untagged: nginx:1.20.0
Deleted: sha256:7ab27dbbfbdf4031f0603a4b597cc43031ff883b54f9329f0309c80952dda6f5
Deleted: sha256:5b2a9404d052ae4205f6139190fd4b0921ddeff17bf2aaf4ee97f79e1a8242fe
Deleted: sha256:03ebf76f0cbf5fd32ca010bb589c2139ce7e44c050fe3de2d77addf4cfd25866
Deleted: sha256:0191669d087dce47072254a93fe55cbedd687f27d3798e2260f846e8f8f5729a
Deleted: sha256:17651c6a0ba04d31da14ac6a86d8fb3f600883f9e155558e8aad0b94aa6540a2
Deleted: sha256:5a673ff4c07a1b606f2ad1fc53697c99c45b0675734ca945e3bb2bd80f43feb8
PS C:\Users\Peng Xiao\docker.tips\image> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest f0b8a9a54136 12 days ago 133MB
PS C:\Users\Peng Xiao\docker.tips\image> docker image load -i .\nginx.image
1839f9962bd8: Loading layer [==================================================>] 64.8MB/64.8MB
a2f4f809e04e: Loading layer [==================================================>] 3.072kB/3.072kB
9b63e6289fbe: Loading layer [==================================================>] 4.096kB/4.096kB
f7141923aaa3: Loading layer [==================================================>] 3.584kB/3.584kB
272bc57d3405: Loading layer [==================================================>] 7.168kB/7.168kB
Loaded image: nginx:1.20.0
PS C:\Users\Peng Xiao\docker.tips\image> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx 1.20.0 7ab27dbbfbdf 12 days ago 133MB
nginx latest f0b8a9a54136 12 days ago 133MB
PS C:\Users\Peng Xiao\docker.tips\image>
3.4、DockerFile介绍
3.4.1、dockerfile简单介绍
- Dockerfile是用于构建docker镜像的文件。
- Dockerfile里包含了构建镜像所需的指令。
- Dockerfile有其特定的语法规则。
举例:执行一个Python程序
假如我们要在一台ubuntu 21.04上运行下面这个hello.py的Python程序,hello.py的文件内容:
print("hello docker")
第一步:准备Python环境
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.10 python3-pip python3.10-dev
第二步:运行hello.py
$ python3 hello.py
hello docker
使用dockerfile构建运行
FROM ubuntu:22.04
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.10 python3-pip python3.10-dev
ADD hello.py /
CMD ["python3", "/hello.py"]
- FROM ubuntu:20.04(为dockerfile选择一个基础镜像导入)
- RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev(安装依赖、包) - ADD hello.py / (将当前目录的hello.py添加到镜像的根目录下面)
- CMD ["python3", "/hello.py"](hello.py文件)
3.5、镜像的构建与分享
3.5.1、docker镜像的构建
docker image build -t hello:1.0 .
-t表示镜像名称。
:表示版本号、tag。
.表示在当前目录构建dockerfile 。
3.5.2、docker启动容器
docker run -it hello
-it 交互式运行
3.5.3、docker镜像的分享(推送到docker hub)
第一步:登陆docker hub
docker login
第二步:推送镜像到docker hub(docker images push docker hub账户名称/推送的镜像名称:tag)
docker image push zkc/hello:1.0
3.5.4、docker镜像重新命名(docker image tag 原tag 新tag)
docker image tag hello test:1.0
3.6、通过commit创建镜像(通过container创建image)
docker container commit containerID 镜像名称:版本号
tips:将容器内配置修改后,生成新的镜像。
3.7、scratch镜像
scratch是一个空的Docker镜像,可以通过scratch来构建一个基础镜像。
举例:运行c语言程序
第一步:创建hello.c 文件
#include <stdio.h>
int main()
{
printf("hello docker\n");
}
第二步:编译成二进制文件
$ gcc --static -o hello hello.c
$ ./hello
hello docker
$
dockerfile构建运行
第一步:创建dockerfile文件
FROM scratch
ADD hello /
CMD ["/hello"]
第二步:构建
$ docker build -t hello .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello latest 2936e77a9daa 40 minutes ago 872kB
第三步:运行
$ docker container run -it hello
hello docker
3.8、 buildkit
Buildkit这个feature能够提高build的效率:Docker Build | Docker Docs
windows:
如果没有,添加:
"features": {
"buildkit": true
}
然后 Apply & Restart
mac:
如果没有,添加
"features": {
"buildkit": true
}
linux:
在 /etc/docker/daemon.json
里添加(如果没有这个文件,则新建), 然后重启docker
{ "features": { "buildkit": true } }
或者在执行docker build命令时设置
$ DOCKER_BUILDKIT=1 docker build .
四、Dockerfile完全指南
4.1、基础镜像的选择
4.1.1、基本原则
- 官方镜像优于非官方镜像,如果没有官方镜像,则尽量选择Dockerfile开源的
- 固定版本tag而不是每次都使用latest
- 尽量选择体积小的镜像
4.1.2、Bulid一个Nginx镜像
第一步:准备一个index.html文件
<h1>Hello Docker</h1>
第二步:准备一个Dockerfile
FROM nginx:1.21.0-alpine
ADD index.html /usr/share/nginx/html/index.html
4.1.3、延申阅读
4.2、通过RUN执行指令
RUN主要用于在Image里执行指令,比如安装软件,下载文件等。
$ 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
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
镜像的大小和分层
$ 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
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
$ 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
$
4.3、文件复制和目录操作
往镜像里复制文件有两种方式,COPY和ADD。
4.3.1、普通复制文件
COPY和ADD都可以把local的一个文件复制到镜像里,如果目标目录不存在,则自动创建。
FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py
比如把本地的hello.py复制到/app目录下。/app这个文件夹不存,则会自动创建。
4.3.2、复制压缩文件
ADD比COPY高级一点的地方就是,如果复制的是一个gzip等压缩文件时,ADD会帮助我们自动取解压缩文件。
FROM python:3.9.5-alpine3.13
ADD hello.tar.gz /app/
因此在COPY和ADD指令中选择的时候,可以遵循这样的原则,所有文件复制均使用COPY指令,仅在需要自动解压缩的场合使用ADD。
WORKDIR 可以用来切换目录,如果路径不存在可以自动创建,相当于进行一个CD的操作
FROM python:3.3.5.alpine3.13
WORKDIR /app
COPY hello.py hello.py
4.4、构建参数和环境变量(ARG VS ENV)
ARG和env是经常容易被混淆的两个Dockerfile的语法,都可以用来设置一个"变量",但实际上两者有很多的不同
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
4.4.1、ENV
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
4.4.2、ARG
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
- ENV 设置的变量可以在Image中保持,并在容器中的环境变量里
$ docker image build -f .\Dockerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSION=2.0.0 .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ipinfo-arg-2.0.0 latest 0d9c964947e2 6 seconds ago 124MB
$ docker container run -it ipinfo-arg-2.0.0
root@b64285579756:/#
root@b64285579756:/# ipinfo version
2.0.0
root@b64285579756:/#
4.5、容器启动命令CMD
CMD可以用来设置容器启动时默认会执行的命令。
- 容器启动时默认执行的命令
- 如果docker container run启动容器时指定了其它命令,则CMD命令会被忽略。
- 如果定义了多个CMD,只有最后一个会被执行。
FROM ubuntu: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
$ docker image build -t ipinfo .
$ docker container run -it ipinfo
root@8cea7e5e8da8:/#
root@8cea7e5e8da8:/#
root@8cea7e5e8da8:/#
root@8cea7e5e8da8:/# pwd
/
root@8cea7e5e8da8:/#
默认进入到shell是因为在ubuntu的基础镜像里有定义CMD
$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
4.6、容器启动命令ENTRYPOINT
ENTRYPOINT也可以设置容器启动时要执行的命令,但是和CMD是有区别的。
- CMD设置的命令,可以在docker container run 时传入其他命令,覆盖掉CMD命令,但是ENTRYPOINT所设置的命令是一定会被执行的。
- ENTRYPOINT和CMD可以联合使用,ENTRYPOINT设置执行的命令,CMD传递参数
CMD
FROM ubuntu:20.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
ENTRYPOINT
FROM ubuntu:20.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 格式
CMD和ENTRYPOINT同时支持shell格式和Exec格式。
4.7.1、Shell格式
CMD echo "hello docker"
ENTRYPOINT echo "hello docker"
4.7.2、Exec格式
以可执行命令的方式
ENTRYPOINT ["echo", "hello docker"]
CMD ["echo", "hello docker"]
4.7.3、注意shell脚本的问题
FROM ubuntu:20.04
ENV NAME=docker
CMD echo "hello $NAME"
假如我们要把上面的CMD改成Exec格式,下面这样改是不行的, 大家可以试试。
FROM ubuntu:20.04
ENV NAME=docker
CMD ["echo", "hello $NAME"]
它会打印出 hello $NAME
, 而不是 hello docker
,那么需要怎么写呢? 我们需要以shell脚本的方式去执行
FROM ubuntu:20.04
ENV NAME=docker
CMD ["sh", "-c", "echo hello $NAME"]
4.8、一起构建一个 Python Flask 镜像
python程序
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
Dockerfile文件
FROM python:3.9.5-slim
COPY app.py /src/app.py
RUN pip install flask
WORKDIR /src
ENV FLASK_APP=app.py
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
4.9、Dockerfile 技巧——合理使用缓存
不经常变化的命令放在Dockerfile文件的前面,这样可以使用缓存。经常变化的,不能使用缓存的放在dockerfile文件的后面,这样即使改动了,也不影响之前命令使用缓存。
优化前Dockerfile
优化后 Dockerfile
改动app.py文件,不影响前面命令使用缓存
4.10、Dockerfile 技巧——合理使用 .dockerignore
4.10.1、什么是Docker build context
Docker是client-server架构,理论上Client和Server可以不在一台机器上。
在构建docker镜像的时候,需要把所需要的文件由CLI(client)发给Server,这些文件实际上就是build context
举例:
$ dockerfile-demo more Dockerfile
FROM python:3.9.5-slim
RUN pip install flask
WORKDIR /src
ENV FLASK_APP=app.py
COPY app.py /src/app.py
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
$ dockerfile-demo more app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, world!'
构建的时候,第一行输出就是发送build context。11.13MB (这里是Linux环境下的log)
$ docker image build -t demo .
Sending build context to Docker daemon 11.13MB
Step 1/7 : FROM python:3.9.5-slim
---> 609da079b03a
Step 2/7 : RUN pip install flask
---> Using cache
---> 955ce495635e
Step 3/7 : WORKDIR /src
---> Using cache
---> 1c2f968e9f9b
Step 4/7 : ENV FLASK_APP=app.py
---> Using cache
---> dceb15b338cf
Step 5/7 : COPY app.py /src/app.py
---> Using cache
---> 0d4dfef28b5f
Step 6/7 : EXPOSE 5000
---> Using cache
---> 203e9865f0d9
Step 7/7 : CMD ["flask", "run", "-h", "0.0.0.0"]
---> Using cache
---> 35b5efae1293
Successfully built 35b5efae1293
Successfully tagged demo:latest
4.10.2、.dockerignore 文件
.vscode/
env/
有了.dockerignore文件后,我们再build, build context就小了很多,4.096kB
$ docker image build -t demo .
Sending build context to Docker daemon 4.096kB
Step 1/7 : FROM python:3.9.5-slim
---> 609da079b03a
Step 2/7 : RUN pip install flask
---> Using cache
---> 955ce495635e
Step 3/7 : WORKDIR /src
---> Using cache
---> 1c2f968e9f9b
Step 4/7 : ENV FLASK_APP=app.py
---> Using cache
---> dceb15b338cf
Step 5/7 : COPY . /src/
---> a9a8f888fef3
Step 6/7 : EXPOSE 5000
---> Running in c71f34d32009
Removing intermediate container c71f34d32009
---> fed6995d5a83
Step 7/7 : CMD ["flask", "run", "-h", "0.0.0.0"]
---> Running in 7ea669f59d5e
Removing intermediate container 7ea669f59d5e
---> 079bae887a47
Successfully built 079bae887a47
Successfully tagged demo:latest
4.11、Dockerfile 技巧——镜像的多阶段构建
4.11.1、C语言例子
假如有一个C的程序,我们想用Docker去做编译,然后执行可执行文件。
#include <stdio.h>
void main(int argc, char *argv[])
{
printf("hello %s\n", argv[argc - 1]);
}
本地测试(如果你本地有C环境)
$ 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
FROM gcc:9.4
COPY hello.c /src/hello.c
WORKDIR /src
RUN gcc --static -o hello hello.c
ENTRYPOINT [ "/src/hello" ]
CMD []
build和测试
$ 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镜像就可以了。
这时候我们就可以使用多阶段构建了。
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 []
测试
$ docker build -t hello-alpine -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-alpine: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
4.11.2、Go语言例子
同样的,假如有一个Go的程序,我们想用Docker去做编译,然后执行可执行文件。
package main
import (
"log"
"net/http"
)
func test(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello golang"))
}
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("start server on [localhost:8080] ...")
http.HandleFunc("/", test)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
本地测试(如果你本地有Golang环境)
$ go build
$ ls
app df dockerfile go.mod main.go
$ ./app
2023/02/15 02:28:18 main.go:14: start server on [localhost:8080] ...
另一个终端
$ curl localhost:8080
Hello golang
构建一个Docker镜像,因为要有Go的环境,所以我们选择golang这个image
FROM golang:alpine3.17 AS builder
COPY main.go /src/app.go
WORKDIR /src
RUN go build app.go
EXPOSE 8080
ENTRYPOINT [ "/src/app" ]
build和测试
$ docker build -t hello-go .
Sending build context to Docker daemon 6.512MB
Step 1/6 : FROM golang:alpine3.17 AS builder
---> 3257bc8ee9f7
Step 2/6 : COPY main.go /src/app.go
---> b0156e003e2d
Step 3/6 : WORKDIR /src
---> Running in 7976422fe214
Removing intermediate container 7976422fe214
---> 122042396c76
Step 4/6 : RUN go build app.go
---> Running in f321f6a73147
Removing intermediate container f321f6a73147
---> 21236778ceee
Step 5/6 : EXPOSE 8080
---> Running in d47b6e2fb836
Removing intermediate container d47b6e2fb836
---> 133988261356
Step 6/6 : ENTRYPOINT [ "/src/app" ]
---> Running in 7f19bd8952b4
Removing intermediate container 7f19bd8952b4
---> 2ccb4f220a22
Successfully built 2ccb4f220a22
Successfully tagged hello-go:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-go latest 2ccb4f220a22 19 minutes ago 321MB
golang alpine3.17 3257bc8ee9f7 3 days ago 254MB
$ docker run -p 8080:8080 -it hello-go
2023/02/14 18:16:11 app.go:14: start server on [localhost:8080] ...
$ curl localhost:8080
Hello golang
可以看到镜像也很大,321MB,同样的,我们使用多阶段构建。
FROM golang:alpine3.17 AS builder
COPY main.go /src/app.go
WORKDIR /src
RUN go build app.go
FROM alpine:3.17.0
COPY --from=builder /src/app /src/app
EXPOSE 8080
ENTRYPOINT [ "/src/app" ]
测试
$ docker build -t hello-go-alpine -f ./df .
Sending build context to Docker daemon 6.512MB
Step 1/8 : FROM golang:alpine3.17 AS builder
---> 3257bc8ee9f7
Step 2/8 : COPY main.go /src/app.go
---> 167672dc57ce
Step 3/8 : WORKDIR /src
---> Running in a53f0f84c92d
Removing intermediate container a53f0f84c92d
---> cc8ee771cdbd
Step 4/8 : RUN go build app.go
---> Running in 9e8e575af675
Removing intermediate container 9e8e575af675
---> e8e7c7219cd5
Step 5/8 : FROM alpine:3.17.0
3.17.0: Pulling from library/alpine
c158987b0551: Pull complete
Digest: sha256:8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4
Status: Downloaded newer image for alpine:3.17.0
---> 49176f190c7e
Step 6/8 : COPY --from=builder /src/app /src/app
---> 8121bedd9a21
Step 7/8 : EXPOSE 8080
---> Running in 93a02551712d
Removing intermediate container 93a02551712d
---> e91f0c467511
Step 8/8 : ENTRYPOINT [ "/src/app" ]
---> Running in aef94175c85d
Removing intermediate container aef94175c85d
---> f3ee197cba4f
Successfully built f3ee197cba4f
Successfully tagged hello-go-alpine:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-go-alpine latest f3ee197cba4f 31 seconds ago 13.6MB
<none> <none> e8e7c7219cd5 46 seconds ago 321MB
hello-go latest 2ccb4f220a22 24 minutes ago 321MB
golang alpine3.17 3257bc8ee9f7 3 days ago 254MB
$ docker run --rm -p 8080:8080 -it hello-go-alpine
2023/02/14 18:42:29 app.go:14: start server on [localhost:8080] ...
现在镜像只有13.6MB
$ curl localhost:8080
Hello golang
4.12、Dockerfile 技巧——尽量使用非root用户
4.12.1、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 ~]$
4.12.2、如何使用非root用户
我们准备两个Dockerfile,第一个Dockerfile如下,其中app.py文件源码请参考 :一起构建一个 Python Flask 镜像 - Docker Tips
FROM python:3.9.5-slim
RUN pip install flask
COPY app.py /src/app.py
WORKDIR /src
ENV FLASK_APP=app.py
EXPOSE 5000
CMD ["flask", "run", "-h", "0.0.0.0"]
假设构建的镜像名字为 flask-demo
第二个Dockerfile,使用非root用户来构建这个镜像,名字叫 flask-no-root
Dockerfile如下:
-
通过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"]
$ 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
分别使用这两个镜像创建两个容器
$ 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 flask-root
五、Docker存储
5.1、Docker存储介绍
默认情况下,在运行中的容器里创建的文件,被保存在一个可写的容器层:
-
如果容器被删除了,则数据也没有了
-
这个可写的容器层是和特定的容器绑定的,也就是这些数据无法方便的和其它容器共享
Docker主要提供了两种方式做数据的持久化
-
Data Volume, 由Docker管理,(/var/lib/docker/volumes/ Linux), 持久化数据的最好方式
-
Bind Mount,由用户指定存储的数据具体mount在系统什么位置
5.2、Data Volume
本节部分操作需要Linux系统的环境,但是大部分都可以在Windows环境下的Docker进行操作,只有一个操作不行。
如何进行数据的持久化。
5.2.1、环境准备
准备一个Dockerfile 和一个 my-cron的文件
$ 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
5.2.2、构建镜像
$ docker image build -t my-cron .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
my-cron latest e9fbd9a562c9 4 seconds ago 24.7MB
5.2.3、创建容器(不指定-v参数)
此时Docker会自动创建一个随机名字的volume,去存储我们在Dockerfile定义的volume VOLUME ["/app"]
$ 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"
}
]
在这个Volume的mountpoint可以发现容器创建的文件
5.2.4、创建容器(指定-v参数)
在创建容器的时候通过 -v
参数我们可以手动的指定需要创建Volume的名字,以及对应于容器内的路径,这个路径是可以任意的,不必需要在Dockerfile里通过VOLUME定义
比如我们把上面的Dockerfile里的VOLUME删除
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参数
$ 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
Volume也创建了。
5.2.5、环境清理
强制删除所有容器,系统清理和volume清理
$ docker rm -f $(docker container ps -aq)
$ docker system prune -f
$ docker volume prune -f
5.3、Data Volume 练习 MySQL
本次练习,演示使用的是Linux环境,Windows环境也可以做这里面的90%以上的内容
使用MySQL官方镜像,tag版本5.7
Dockerfile可以在这里查看 https://github.com/docker-library/mysql/tree/master/5.7
5.3.1、准备镜像
$ docker pull mysql:5.7
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 2c9028880e58 5 weeks ago 447MB
5.3.2、创建容器
关于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
$ 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"
}
]
$
5.3.3、数据库写入数据
进入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.3.4、其它数据库
如果熟悉的话,也可以试试MongoDB https://hub.docker.com/_/mongo
5.4、Bind Mount
本节部分操作需要Windows系统的环境,其他系统环境也可以
5.2.1、环境准备
准备一个Dockerfile 和一个 my-cron的文件
$ 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
5.2.2、构建镜像
$ docker image build -t my-cron .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
my-cron latest e9fbd9a562c9 4 seconds ago 24.7MB
5.2.3、创建容器
添加-v参数指定系统路径映射容器路径
docker container run -d -v ${pwd}:/app my-cron
表示当前路径映射容器/app路径。windows当前路径使用${},mac和linux使用$()。
5.5、多个机器之间的容器共享数据
官方参考链接 Volumes | Docker Docs
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
5.5.1、环境准备
准备三台Linux机器,之间可以通过SSH相互通信。
5.5.2、安装plugin
在其中两台机器上安装一个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
5.5.3、创建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"
}
]
5.5.4、创建容器挂载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网络
6.1、网络基础知识回顾
6.1.1、什么是IP、子网掩码、网关、DNS、端口号
网址:https://zhuanlan.zhihu.com/p/65226634
6.1.2、面试常问的一个题目, 当你在浏览器中输入一个网址(比如www.baidu.com)并敲回车,这个过程后面都发生了什么
Internet如何工作的:https://www.hp.com/us-en/shop/tech-takes/how-does-the-internet-work
从数据包的角度详细解析:Traffic Example, step-by-step - Homenet Howto
6.2、网络常用命令
6.2.1、IP地址的查看
Windows
ipconfig
Linux
ifconfig
ip addr
6.2.2、网络连通性测试
A、ping命令(测试本地服务器和目标服务器的可达性)
PS C:\Users\Peng Xiao> 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
PS C:\Users\Peng Xiao>
B、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.
➜ ~
C、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\Peng Xiao> 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
D、curl命令(请求web服务的)
使用教程网址:curl 的用法指南 - 阮一峰的网络日志
6.3、Docker Bridge 网络
6.3.1、创建两个容器
$ docker container run -d --rm --name box1 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container run -d --rm --name box2 busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4f3303c84e53 busybox "/bin/sh -c 'while t…" 49 minutes ago Up 49 minutes box2
03494b034694 busybox "/bin/sh -c 'while t…" 49 minutes ago Up 49 minutes box1
6.3.2、容器间通信
容器连接在同一个bridge,则网络可以互通,否则网络不可以互通。一个容器可以连接多个bridge。
两个容器都连接到了一个叫 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
使用前需要安装, 对于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
6.3.3、容器对外通信
查看路由
$ 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
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
6.3.4、端口转发
创建容器
$ docker container run -d --rm --name web -p 8080:80 nginx
$ docker container inspect --format '{{.NetworkSettings.IPAddress}}' web
$ docker container run -d --rm --name client busybox /bin/sh -c "while true; do sleep 3600; done"
$ docker container inspect --format '{{.NetworkSettings.IPAddress}}' client
$ docker container exec -it client wget http://172.17.0.2
查看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 to:172.17.0.2:80
6.3.5、参考资料
网址:7.4. FORWARD and NAT Rules | Red Hat Product Documentation
6.4、创建和使用bridge
6.4.1、创建bridge命令
// -d指定driver类型为bridge,mybridge为自定义bridge名称
docker network create -d bridge mybridge
查看创建的bridge
docker network ls
查看创建的bridge详细信息
docker network inspect mybrige
6.4.2、使用创建bridge
启动容器添加参数使用 --network bridge 名称。
举例:
$ docker container run -d --rm --name box1 --network mybridge busybox /bin/sh -c "while true; do sleep 3600; done"
6.4.3、docker容器连接bridge
一个容器可以连接多个bridge
//docker network connect bridge名称 容器名称
docker network connect bridge box3
6.4.4、docker容器断开连接bridge
//docker network disconnect bridge名称 容器名称
docker network disconnect bridge box3
默认bridge不提供dns功能,ping只能使用IP地址,创建的bridge可以使用dns功能,ping可以使用ip或者容器名称
6.4.5、创建bridge参数讲解
-d:指定bridgeDRIVER类型
--gateway strings:指定IPAM Config中的Gateway的值
--subnet strings:指定IPAM Config中的Subnet的值
例如:
docker network create -d bridge --gateway 172.200.0.1 --subnet 172.200.0.0/16 testdemo
6.5、Docker Host None网络
Docker Host网络:容器网络和宿主机共享同一个网络。每个容器网络端口不隔离,比如用了80启动,其他容器启动不可以再使用80,性能会高一点,因为少了网络转换。
Docker bridge网络:容器网络和宿主机网络隔离,单独的网络。每个容器网络端口隔离,比如用了80端口启动,其他容器启动还可以使用80端口。
Docker None网络:不能被外部访问,一般用作其他容器使用,网络由其他容器控制。
6.6、网络命名空间
Linux的Namespace(命名空间)技术是一种隔离技术,常用的Namespace有 user namespace, process namespace, network namespace等
在Docker容器中,不同的容器通过Network namespace进行了隔离,也就是不同的容器有各自的IP地址,路由表等,互不影响。
准备一台Linux机器,这一节会用到一个叫
brtcl
的命令,这个命令需要安装,如果是Ubuntu的系统,可以通过apt-get install bridge-utils
安装;如果是Centos系统,可以通过sudo yum install bridge-utils
来安装
6.6.1、创建bridge
[vagrant@docker-host1 ~]$ sudo brctl addbr mydocker0
[vagrant@docker-host1 ~]$ brctl show
bridge name bridge id STP enabled interfaces
mydocker0 8000.000000000000 no
[vagrant@docker-host1 ~]$
6.6.2、准备一个shell脚本
https://twitter.com/xiaopeng163/status/1531022226933391362?s=20&t=LuWDZHV3TCLmLsI1nCb1FQ
脚本名字叫 add-ns-to-br.sh
#!/bin/bash
bridge=$1
namespace=$2
addr=$3
vethA=veth-$namespace
vethB=eth00-$namespace
sudo ip netns add $namespace
sudo ip link add $vethA type veth peer name $vethB
sudo ip link set $vethB netns $namespace
sudo ip netns exec $namespace ip addr add $addr dev $vethB
sudo ip netns exec $namespace ip link set $vethB up
sudo ip link set $vethA up
sudo brctl addif $bridge $vethA
6.6.3、脚本执行
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns1 172.16.1.1/16
[vagrant@docker-host1 ~]$ sh add-ns-to-br.sh mydocker0 ns2 172.16.1.2/16
把mydocker0这个bridge up起来
[vagrant@docker-host1 ~]$ sudo ip link set dev mydocker0 up
6.6.4、验证
[vagrant@docker-host1 ~]$ sudo ip netns exec ns1 bash
[root@docker-host1 vagrant]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: eth00@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether f2:59:19:34:73:70 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.16.1.1/16 scope global eth00
valid_lft forever preferred_lft forever
inet6 fe80::f059:19ff:fe34:7370/64 scope link
valid_lft forever preferred_lft forever
[root@docker-host1 vagrant]# ping 172.16.1.2
PING 172.16.1.2 (172.16.1.2) 56(84) bytes of data.
64 bytes from 172.16.1.2: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 172.16.1.2: icmp_seq=2 ttl=64 time=0.080 ms
^C
--- 172.16.1.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.029/0.054/0.080/0.026 ms
[root@docker-host1 vagrant]#
6.6.5、对外通信
参考资料:NAT with Linux and iptables - Tutorial (Introduction)
6.7、Python Flask + Redis 练习
6.7.1、程序准备
准备一个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"]
6.7.2、镜像准备
构建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
6.7.3、创建一个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
$
6.7.4、创建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
$
6.7.5、创建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.
6.7.6、总结
如果把上面的步骤合并到一起,成为一个部署脚本
# 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 5000:5000 flask-demo