Docker学习笔记

一、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、 参考资料

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环境的搭建系列视频

1.7、Docker Machine 搭建 docker 环境

文档地址:Deprecated products and features | Docker Docs

1.8、通过 Vagrant 搭建 Linux Docker 环境

Vagrant是一个快速创建虚拟机的工具,可以参考youtube或者B站的Vagrant入门系列视频

1.9、本节常见问题

  1.  windows 10 家庭版能装 Hyper-v 么?

  2. 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
  1. 在本地查找是否有nginx这个image镜像,但是没有发现
  2. 去远程的image registry查找nginx镜像(默认的registry是Docker Hub)
  3. 下载最新版本的nginx镜像 (nginx:latest 默认)
  4. 基于nginx镜像来创建一个新的容器,并且准备运行
  5. docker engine分配给这个容器一个虚拟IP地址
  6. 在宿主机上打开80端口并把容器的80端口转发到宿主机上
  7. 启动容器,运行指定的命令(这里是一个shell脚本去启动nginx)
  8. -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

七、Docker compose

八、Docker swarm

九、Docker VS podman

十、Docker的多架构支持

十一、Git和容器-----CI-CD

十二、容器安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值