Docker 完整指南:从入门到企业实战

1.docker-ce部署

CentOS (使用 yum 进行安装)

在 CentOS 上装 Docker,先得把系统里必要的工具装上,这些工具能帮我们管理软件源和处理存储。然后换个国内的软件源(阿里云),下载速度快。接着直接装 Docker 的核心组件,最后启动服务并设为开机自启,这样 Docker 就随时能用了。

bash 复制代码
# step 1: 安装必要的一些系统工具
sudo yum install -y yum-utils

# Step 2: 添加软件源信息
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# Step 3: 安装Docker
sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Step 4: 开启Docker服务
sudo service docker start

# 注意:
# 官方软件源默认启用了最新的软件,您可以通过编辑软件源的方式获取各个版本的软件包。例如官方并没有将测试版本的软件源置为可用,您可以通过以下方式开启。同理可以开启各种测试版本等。
# vim /etc/yum.repos.d/docker-ce.repo
#   将[docker-ce-test]下方的enabled=0修改为enabled=1
#
# 安装指定版本的Docker-CE:
# Step 1: 查找Docker-CE的版本:
# yum list docker-ce.x86_64 --showduplicates | sort -r
#   Loading mirror speeds from cached hostfile
#   Loaded plugins: branch, fastestmirror, langpacks
#   docker-ce.x86_64            17.03.1.ce-1.el7.centos            docker-ce-stable
#   docker-ce.x86_64            17.03.1.ce-1.el7.centos            @docker-ce-stable
#   docker-ce.x86_64            17.03.0.ce-1.el7.centos            docker-ce-stable
#   Available Packages
# Step2: 安装指定版本的Docker-CE: (VERSION例如上面的17.03.0.ce.1-1.el7.centos)
# sudo yum -y install docker-ce-[VERSION]

Ubuntu(使用 apt-get 进行安装)

Ubuntu 装 Docker 和 CentOS 类似,但用的是 apt 工具。

bash 复制代码
# step 1: 安装必要的一些系统工具
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg

# step 2: 信任 Docker 的 GPG 公钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Step 3: 写入软件源信息
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
 
# Step 4: 安装Docker
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 安装指定版本的Docker-CE:
# Step 1: 查找Docker-CE的版本:
# apt-cache madison docker-ce
#   docker-ce | 17.03.1~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
#   docker-ce | 17.03.0~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
# Step 2: 安装指定版本的Docker-CE: (VERSION例如上面的17.03.1~ce-0~ubuntu-xenial)
# sudo apt-get -y install docker-ce=[VERSION]

openEluer(使用dnf安装)

openEuler 装 Docker,先加个 CentOS 的 Docker 源(因为兼容)。但源里的版本变量不对,得手动改成 8。然后配置镜像加速器,让拉镜像更快。最后重启 Docker,拉个小镜像试试,能拉下来就说明成了。

bash 复制代码
# step 1 获取docker的yum源
dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# step 2 将其中$release修改为8(% s/$release/8/)
[root@openEuler-1 ~]# cat /etc/yum.repos.d/docker-ce.repo 
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-stable-debuginfo]
name=Docker CE Stable - Debuginfo $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/debug-$basearch/stable
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-stable-source]
name=Docker CE Stable - Sources
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/source/stable
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-test]
name=Docker CE Test - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/$basearch/test
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-test-debuginfo]
name=Docker CE Test - Debuginfo $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/debug-$basearch/test
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-test-source]
name=Docker CE Test - Sources
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/source/test
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-nightly]
name=Docker CE Nightly - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/$basearch/nightly
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-nightly-debuginfo]
name=Docker CE Nightly - Debuginfo $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/debug-$basearch/nightly
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

[docker-ce-nightly-source]
name=Docker CE Nightly - Sources
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/source/nightly
enabled=0
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg


# step 3 配置镜像加速器
[root@openEuler-1 ~]# cat /etc/docker/daemon.json 
{
    "registry-mirrors": [
            "https://docker-0.unsee.tech",
    		"https://docker.xuanyuan.me",
    		"https://docker.1panel.live",
    		"https://docker.m.daocloud.io"
    ]
}
systemctl daemon-reload
sudo systemctl restart docker

# step 4 测试
[root@openEuler-1 ~]# docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
80bfbb8a41a2: Pull complete 
Digest: sha256:ab33eacc8251e3807b85bb6dba570e4698c3998eca6f0fc2ccb60575a563ea74
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
[root@openEuler-1 ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
busybox      latest    0ed463b26dae   11 months ago   4.43MB

2.操作docker镜像

2.1 获取镜像

要想用某个软件的镜像,得先从网上拉到本地。就像下载软件安装包,用 docker pull 命令,指定镜像名和版本(标签)就行,没写版本就默认最新的。通过 docker pull 从远程仓库(如 Docker Hub)拉取镜像到本地,格式为:

bash 复制代码
docker pull <镜像名>:<标签>  # 如 docker pull nginx:latest

若不指定标签,默认拉取 latest 版本。

2.2 查看镜像信息

images 看概览,inspect 看细节,history 看构建步骤。

查看本地所有镜像的基本信息(仓库、标签、ID、大小等):

bash 复制代码
docker images  # 或 docker image ls

查看镜像的详细元数据(如架构、创建历史、层信息等):

复制代码
docker inspect <镜像ID/名称>  # 如 docker inspect nginx

查看镜像的分层历史:

复制代码
docker history <镜像ID/名称>

2.3 搜寻镜像

通过 docker search 在 Docker Hub 中搜索镜像,格式为:

复制代码
docker search <关键词>  # 如 docker search java

可结合参数过滤结果(如只显示星级≥1000 的镜像):

复制代码
docker search -f stars=1000 nginx

2.4 删除和清理镜像

单个删用 rmi,批量清理用 prune。注意删之前确保镜像没被容器用,不然删不掉,或者加 - f 强制删。

复制代码
docker rmi <镜像ID/名称>  # 如 docker rmi nginx:latest

清理 无标签(dangling)的镜像:

复制代码
docker image prune

清理所有未被容器使用的镜像(含带标签的镜像):

复制代码
docker image prune -a

2.5 存出和载入镜像

有时候需要把镜像拷贝到没网的机器上用,就先把镜像存成 tar 文件(save),拷过去后再导入(load),这样不用重新从网上拉了。

存出镜像(将镜像保存为本地文件):

复制代码
docker save -o <输出文件名.tar> <镜像名>:<标签>  # 如 docker save -o myapp.tar myapp:v1

载入镜像(从本地文件导入镜像):

复制代码
docker load -i <镜像文件.tar>  # 如 docker load -i myapp.tar

3.docker容器操作

3.1 启动容器

启动容器就是让镜像跑起来。后台启动用 - d,想进去操作就加 - it。端口映射能让外部访问容器里的服务。attach 和 exec 都能进容器,但 exec 更安全(不影响主进程)。

bash 复制代码
[root@docker-ce ~]# docker run -d --name nginx -p 80:80 nginx:latest
#进入到容器中,按<ctrl>+<d>退出并停止容器,#按<ctrl>+<pq>退出但不停止容器
[root@docker-ce ~]# docker run -it --name busybox busybox:latest 
/ # 
/ # 
/ # 
#重新进入容器
[root@docker ~]# docker attach centos7
[root@3ba22e59734f /]#

#在容器中执行命令
[root@docker ~]# docker exec -it  test ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

attach(缠绕):在后台运行的容器,将其放到前端来,还是一个进程;如果前台运行的容器,再开一个终端,就会结束原来终端的容器,相同容器相同进程

exec:在运行的容器的原有一个进程基础上,再开启一个进程,相同容器不同进程

!NOTE

bash 复制代码
-d			#后台运行
-i			#交互式运行
-t			#打开一个终端
--name		#指定容器名称
-p			#端口映射 -p 80:8080	把容器8080端口映射到本机80端口
--rm		#容器停止自动删除容器
--network 	#指定容器使用的网络

3.2 查看容器运行信息

想知道容器有没有在跑、有哪些容器,用 ps 或 ps -a。想了解某个容器的详细情况(比如 IP 地址、挂载了啥),就用 inspect。

bash 复制代码
[root@Docker-node1 ~]# docker ps					#查看当前运行容器
[root@Docker-node1 ~]# docker ps -a					#查看所有容器
[root@Docker-node1 ~]# docker inspect busybox		#查看容器运行的详细信息

3.3 停止和运行容器

stop 是正常停,kill 是强制停。想再用的话,用 start 启动就行。

bash 复制代码
[root@Docker-node1 ~]# docker stop busybox			#停止容器
[root@Docker-node1 ~]# docker kill busybox			#杀死容器,可以使用信号
[root@Docker-node1 ~]# docker start busybox			#开启停止的容器

!NOTE

容器内的第一个进程必须一直处于运行的状态,否则这个容器,就会处于退出状态!

3.4 删除容器

普通 rm 删停止的,加 - f 能删运行中的。想一次性清掉所有停掉的容器,用 prune -f 更方便。

bash 复制代码
[root@Docker-node1 ~]# docker rm centos7			#删除停止的容器

[root@Docker-node1 ~]# docker rm -f busybox			#删除运行的容器

[root@Docker-node1 ~]# docker container prune -f	#删除所有停止的容器

3.5 系统中的文件和容器中的文件传输

有时候需要把本机文件传到容器里(比如改个配置),或者把容器里的文件拿出来(比如日志),用 docker cp 命令就行,格式和 Linux 的 cp 类似,先写源路径,再写目标路径。

bash 复制代码
[root@docker-ce ~]# vim /mnt/index.html
[root@docker-ce ~]# cat /mnt/index.html 
rch
#把本机文件复制到容器中
[root@docker-ce ~]# docker run -d --name webserver -p 80:80 nginx:latest 
861ac47879157284758a0cbc288bfcebbcfd404c1b2ad19afb1f64b70a5f2fe9
[root@docker-ce ~]# docker cp /mnt/index.html webserver:/usr/share/nginx/html
Successfully copied 2.05kB to webserver:/usr/share/nginx/html

#把容器中的文件复制到本机
[root@docker-ce ~]# docker cp  webserver:/usr/share/nginx/html /mnt	
Successfully copied 1.54kB to /mnt

3.6 容器内容提交

在容器里改了东西(比如改了配置、加了文件),想把这些改动永久保存,就用 commit 生成新镜像。以后用这个新镜像启动容器,改动就还在,不怕原容器被删。

bash 复制代码
[root@docker-ce ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS         PORTS                                 NAMES
861ac4787915   nginx:latest   "/docker-entrypoint...."   6 minutes ago   Up 6 minutes   0.0.0.0:80->80/tcp, [::]:80->80/tcp   webserver
[root@docker-ce ~]# docker commit -m "change index" webserver nginx:new
sha256:bca64c3d48a347c7da183bb27820360d35a0ffbce32d6ebd4b9d511b2345a5a1
[root@docker-ce ~]# docker rm -f webserver
^[[Awebserver
[root@docker-ce ~]# docker run -d --name rch nginx:new

3.7 查询容器内部日志

用 logs 命令看日志,能看到启动过程、错误信息、访问记录等,方便排查问题。

bash 复制代码
[root@Docker-node1 ~]# docker logs web
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2024/08/14 07:50:01 [notice] 1#1: using the "epoll" event method
2024/08/14 07:50:01 [notice] 1#1: nginx/1.27.0
2024/08/14 07:50:01 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
2024/08/14 07:50:01 [notice] 1#1: OS: Linux 5.14.0-427.13.1.el9_4.x86_64
2024/08/14 07:50:01 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1073741816:1073741816
2024/08/14 07:50:01 [notice] 1#1: start worker processes
2024/08/14 07:50:01 [notice] 1#1: start worker process 29
2024/08/14 07:50:01 [notice] 1#1: start worker process 30
172.17.0.1 - - [14/Aug/2024:07:50:20 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.76.1" "-"

4.docker镜像构建

4.1 docker镜像结构

镜像就像叠汉堡,一层一层堆起来的。底层是共享主机的内核,上面是基础镜像(最小系统),再往上是我们加的软件和配置。分层的好处是共享资源,比如多个镜像用同一个基础层,就只存一份,省空间。

  • 共享宿主机的kernel
  • base镜像提供的是最小的Linux发行版
  • 同一docker主机支持运行多种Linux发行版
  • 采用分层结构的最大好处是:共享资源

4.2 镜像运行的基本原理

镜像本身是只读的,启动容器时会在镜像上面加一层可写层(容器层)。容器里改的文件都存在这层,不会动到底下的镜像层。这样多个容器可以共享一个镜像,各自的改动互不影响。

  • Copy-on-Write 可写容器层
  • 容器层以下所有镜像层都是只读的
  • docker从上往下依次查找文件
  • 容器层保存镜像变化的部分,并不会对镜像本身进行任何修改
  • 一个镜像最多127层

4.3 镜像获得方式

要么从网上拉(docker pull),要么用本地的 tar 包导(docker load),企业里通常用官方镜像改改(Dockerfile)做出自己的镜像。

  • 基本镜像通常由软件官方提供

  • 企业镜像可以用官方镜像+Dockerfile来生成

  • 系统关于镜像的获取动作有两种:

    • docker pull 镜像地址

    • docker load --i 本地镜像包

4.4 镜像构建

4.4.1 构建参数

FROM 指定base镜像 eg:FROM busybox:version
COPY 复制文件 eg:COPY file /file 或者 COPY ["file","/"]
MAINTAINER 指定作者信息,比如邮箱 eg:MAINTAINER user@example.com 在最新版的docker中用LABEL KEY="VALUE"代替
ADD 功能和copy相似,指定压缩文件或url eg: ADD test.tar /mnt 或者 eg:ADD http://ip/test.tar /mnt
ENV 指定环境变量 eg:ENV FILENAME test
EXPOSE 暴漏容器端口 eg:EXPOSE 80
VOLUME 申明数据卷,通常指数据挂载点 eg:VOLUME ["/var/www/html"]
WORKDIR 切换路径 eg:WORKDIR /mnt
RUN 在容器中运行的指令 eg: touch file
CMD 在启动容器时自动运行动作可以被覆盖 eg:CMD echo FILENAME 会调用shell解析 eg:CMD \["/bin/sh","-c","echo FILENAME"] 不调用shell解析
ENTRYPOINT 和CMD功能和用法类似,但动作不可被覆盖

4.5 镜像优化方案

bash 复制代码
# 原始构建的文件和Dockerfile,构建出的镜像有462MB,太大了
[root@docker-ce docker]# ll
总用量 23036
-rw-r--r-- 1 root root 22456832 10月  9 16:59 debian11.tar.gz
-rw-r--r-- 1 root root      743 10月 10 00:33 Dockerfile
-rw-r--r-- 1 root root        6 10月  9 17:53 index.html
-rw-r--r-- 1 root root      201 10月  9 17:59 mnt.tar.gz
-rw-r--r-- 1 root root  1112471 10月  9 16:20 nginx-1.24.0.tar.gz
-rw-r--r-- 1 root root       98 10月  9 17:46 rhel7.repo
[root@docker-ce docker]# cat Dockerfile 
from centos:7
# 替换yum源为阿里云,避免官方源慢
RUN rm -f /etc/yum.repos.d/*.repo && \
    echo '[centos]' > /etc/yum.repos.d/centos.repo && \
    echo 'name=centos' >> /etc/yum.repos.d/centos.repo && \
    echo 'baseurl=https://mirrors.aliyun.com/centos/7.9.2009/os/x86_64/' >> /etc/yum.repos.d/centos.repo && \
    echo 'gpgcheck=0' >> /etc/yum.repos.d/centos.repo
# 清理yum缓存并生成新缓存
RUN yum clean all && yum makecache
# 把nginx源码包加到镜像的/mnt目录
add nginx-1.24.0.tar.gz /mnt
# 切换工作目录到nginx源码目录
workdir /mnt/nginx-1.24.0
# 安装编译依赖
run yum install -y gcc make pcre-devel openssl-devel
# 修改编译配置,去掉调试信息(减小体积)
run sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc
# 配置nginx编译参数(启用ssl等模块)
run ./configure --with-http_ssl_module --with-http_stub_status_module
# 编译并安装nginx
run make 
run make install
# 声明暴露80端口
expose 80
# 声明数据卷(存放网页文件)
VOLUME ["/usr/local/nginx/html"]
# 启动nginx(前台运行,避免容器退出)
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
[root@docker-ce docker]# docker build -t nginx:V1 .
[root@docker-ce docker]# docker images
REPOSITORY                        TAG       IMAGE ID       CREATED              SIZE
nginx                             V1        986c8810ff86   About a minute ago   462MB

发现构建的镜像容量十分大,所以需要镜像优化

4.5.1 镜像优化策略

镜像太大了不好传也占空间,得优化。方法有三种:一是少建几层(合并命令),二是多阶段构建(编译和运行分开,只留运行需要的),三是用更小的基础镜像(去掉没用的系统组件)。这样能把镜像体积大幅缩小。

  • 选择最精简的基础镜像
  • 减少镜像的层数
  • 清理镜像构建的中间产物

方法一.缩减镜像层(把多个 RUN 命令合并,减少层数,同时清理中间文件)

bash 复制代码
[root@docker-ce docker]# cat Dockerfile 
from centos:7.9
add nginx-1.24.0.tar.gz /mnt
workdir /mnt/nginx-1.24.0
# 把安装依赖、编译、安装、清理的命令合并成一个RUN,减少层数,同时删除源码和yum缓存(减小体积)
run yum install -y gcc make pcre-devel openssl-devel && sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc && ./configure --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -rf nginx-1.24.0 && yum clean all 
expose 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
[root@docker-ce docker]# docker build -t webserver:v2 .
[root@docker-ce docker]# docker images webserver ## 比V1小了32MB
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
webserver    v2        03ae48f57e29   2 minutes ago   430MB

方法二.多阶段构建(先在一个镜像里编译,再把结果复制到另一个精简镜像,去掉编译工具)

bash 复制代码
[root@docker-ce docker]# cat Dockerfile 
# 第一阶段:用centos:7.9做基础,编译安装nginx
from centos:7.9 as base 
add nginx-1.24.0.tar.gz /mnt
workdir /mnt/nginx-1.24.0
run yum install -y gcc make pcre-devel openssl-devel && sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc && ./configure --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -rf nginx-1.24.0 && yum clean all 

# 第二阶段:用干净的centos:7.9,只复制第一阶段编译好的nginx,去掉编译工具
from centos:7.9
copy --from=base /usr/local/nginx/ /usr/local/nginx/
expose 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
[root@docker-ce docker]# docker build -t webserver:v3 .
[root@docker-ce docker]# docker images webserver
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
webserver    v3        e315cee239d3   17 seconds ago   346MB

方法三.使用最精简镜像(比如nginx官方提供的debian11,去掉不必要的系统组件)

bash 复制代码
[root@docker-ce docker]# ll
总用量 23036
-rw-r--r-- 1 root root 22456832 10月  9 16:59 debian11.tar.gz	# 精简的debian11基础镜像
-rw-r--r-- 1 root root      506 10月 10 17:49 Dockerfile
-rw-r--r-- 1 root root        6 10月  9 17:53 index.html
-rw-r--r-- 1 root root      201 10月  9 17:59 mnt.tar.gz
-rw-r--r-- 1 root root  1112471 10月  9 16:20 nginx-1.24.0.tar.gz
-rw-r--r-- 1 root root       98 10月  9 17:46 rhel7.repo
[root@docker-ce docker]# docker load -i debian11.tar.gz		# 导入精简镜像
[root@docker-ce docker]# docker images
REPOSITORY                        TAG       IMAGE ID       CREATED   
gcr.io/distroless/base-debian11   latest    2a6de77407bf   N/A              20.6MB
[root@docker-ce docker]# cat Dockerfile 
# 第一阶段:用官方nginx镜像做基础,提取需要的文件
from nginx:1.24 as base
ARG TIME_ZONE
RUN mkdir -p /opt/var/cache/nginx && \
	# 复制nginx相关的库和程序
    cp -a --parents /usr/lib/nginx /opt && \
    cp -a --parents /usr/share/nginx /opt && \
    cp -a --parents /var/log/nginx /opt && \
    cp -aL --parents /var/run /opt && \
    cp -a --parents /etc/nginx /opt && \
    cp -a --parents /etc/passwd /opt && \
    cp -a --parents /etc/group /opt && \
    cp -a --parents /usr/sbin/nginx /opt && \
    cp -a --parents /usr/sbin/nginx-debug /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/ld-* /opt && \
    cp -a --parents /usr/lib/x86_64-linux-gnu/libpcre* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libz.so.* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libc* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libdl* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libpthread* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libcrypt* /opt && \
    cp -a --parents /usr/lib/x86_64-linux-gnu/libssl.so.* /opt && \
    cp -a --parents /usr/lib/x86_64-linux-gnu/libcrypto.so.* /opt && \		
    # 复制时区文件
    cp /usr/share/zoneinfo/${TIME_ZONE:-ROC} /opt/etc/localtime

# 第二阶段:用精简的debian11镜像,只复制第一阶段需要的文件
from gcr.io/distroless/base-debian11
copy --from=base /opt /		# 把第一阶段的/opt目录复制到当前镜像的根目录
EXPOSE 80 443
ENTRYPOINT ["nginx", "-g", "daemon off;"]
[root@docker-ce docker]# docker build -t webserver:v5 .
[root@docker-ce docker]# docker images webserver
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
webserver    v5        88caa3ed7e9f   25 hours ago     34.6MB

5.docker 镜像仓库的管理

5.1 什么是docker仓库

Docker 仓库(Docker Registry) 是用于存储和分发 Docker 镜像的集中式存储库。

它就像是一个大型的镜像仓库,开发者可以将自己创建的 Docker 镜像推送到仓库中,也可以从仓库中拉取所需的镜像。

Docker 仓库可以分为公共仓库和私有仓库:

  • 公共仓库,如 Docker Hub,任何人都可以访问和使用其中的镜像。许多常用的软件和应用都有在 Docker Hub 上提供的镜像,方便用户直接获取和使用。
    • 例如,您想要部署一个 Nginx 服务器,就可以从 Docker Hub 上拉取 Nginx 的镜像。
  • 私有仓库则是由组织或个人自己搭建和管理的,用于存储内部使用的、不希望公开的镜像。
    • 比如,一家企业为其特定的业务应用创建了定制化的镜像,并将其存储在自己的私有仓库中,以保证安全性和控制访问权限。

通过 Docker 仓库,开发者能够方便地共享和复用镜像,加速应用的开发和部署过程。

5.2 docker hub

官网:https://hub.docker.com/

Docker Hub 是 Docker 官方提供的一个公共的镜像仓库服务。

它是 Docker 生态系统中最知名和广泛使用的镜像仓库之一,拥有大量的官方和社区贡献的镜像。

以下是 Docker Hub 的一些关键特点和优势:

  1. 丰富的镜像资源:涵盖了各种常见的操作系统、编程语言运行时、数据库、Web 服务器等众多应用的镜像。
    • 例如,您可以轻松找到 Ubuntu、CentOS 等操作系统的镜像,以及 MySQL、Redis 等数据库的镜像。
  2. 官方支持:提供了由 Docker 官方维护的一些重要镜像,确保其质量和安全性。
  3. 社区贡献:开发者们可以自由上传和分享他们创建的镜像,促进了知识和资源的共享。
  4. 版本管理:对于每个镜像,通常都有多个版本可供选择,方便用户根据需求获取特定版本。
  5. 便于搜索:用户可以通过关键词轻松搜索到所需的镜像。

5.2.1 docker hub的使用方法

bash 复制代码
#登陆官方仓库
[root@docker ~]# docker login
Log in with your Docker ID or email address to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com/ to create one.
You can log in with your password or a Personal Access Token (PAT). Using a limited-scope PAT grants better security and is required for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/

Username: timinglee
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores

Login Succeeded

#登陆信息保存位置
[root@docker ~]# cd .docker/
[root@docker .docker]# ls
config.json
[root@docker .docker]# cat config.json
{
        "auths": {
                "https://index.docker.io/v1/": {
                        "auth": "dGltaW5nbGVlOjY3NTE1MTVtaW5nemxu"
                }
        }

[root@docker ~]# docker tag gcr.io/distroless/base-debian11:latest  timinglee/base-debian11:latest
[root@docker ~]# docker push  timinglee/base-debian11:latest
The push refers to repository [docker.io/timinglee/base-debian11]
6835249f577a: Pushed
24aacbf97031: Pushed
8451c71f8c1e: Pushed
2388d21e8e2b: Pushed
c048279a7d9f: Pushed
1a73b54f556b: Pushed
2a92d6ac9e4f: Pushed
bbb6cacb8c82: Pushed
ac805962e479: Pushed
af5aa97ebe6c: Pushed
4d049f83d9cf: Pushed
9ed498e122b2: Pushed
577c8ee06f39: Pushed
5342a2647e87: Pushed
latest: digest: sha256:f8179c20f1f2b1168665003412197549bd4faab5ccc1b140c666f9b8aa958042 size: 3234

!TIP

在国内因为网络等原因,docker hub连接困难,可以使用国内的镜像加速解决镜像无法下载的问题

bash 复制代码
[root@harbor docker]# vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://docker.m.daocloud.io"],
}
[root@harbor docker]# systemctl restart docker

5.3 docker仓库的工作原理

仓库中的三个角色

index docker索引服务,负责并维护有关用户帐户、镜像的校验以及公共命名空间的信息。

registry docker仓库,是镜像和图表的仓库,它不具有本地数据库以及不提供用户认证,通过Index Auth service的Token的方式进行认证

Registry Client Docker充当registry客户端来维护推送和拉取,以及客户端的授权。

5.3.1 pull原理

镜像拉取分为以下几步:

1.docker客户端向index发送镜像拉去请求并完成与index的认证

2.index发送认证token和镜像位置给dockerclient

3.dockerclient携带token和根据index指引的镜像位置取连接registry

4.Registry会根据client持有的token跟index核实身份合法性

5.index确认此token合法性

6.Registry会根据client的请求传递镜像到客户端

5.3.2 push原理

镜像上传的步骤:

1.client向index发送上传请求并完成用户认证

2.index会发方token给client来证明client的合法性

3.client携带index提供的token连接Registry

4.Registry向index合适token的合法性

5.index证实token的合法性

6.Registry开始接收客户端上传过来的镜像

5.3 搭建docker的私有仓库

5.3.1 为什么搭建私有仓库

docker hub虽然方便,但是还是有限制

  • 需要internet连接,速度慢
  • 所有人都可以访问
  • 由于安全原因企业不允许将镜像放到外网

好消息是docker公司已经将registry开源,我们可以快速构建企业私有仓库

地址: https://docs.docker.com/registry/deploying/

5.4 为Registry提加密传输----https

私有仓库默认用 http,不安全,所以要配 https。先生成证书和密钥,然后启动仓库时配上这些证书。客户端要信任这个证书,就得把证书放到指定目录。最后给镜像打个带仓库域名的标签,就能上传了,这样传输过程是加密的。

192.168.2.50 既做客户端又做仓库(就是用它做仓库,也用它拉镜像)
  • 首先需要生成 https 需要的证书和密钥(相当于给仓库做一套 "加密身份证")
bash 复制代码
[root@docker-ce ~]# mkdir certs  # 创建存放证书的目录
[root@docker-ce ~]# 
# 生成RSA密钥(4096位)和自签名证书,有效期365天,指定域名rch.hjn.com
openssl req -newkey  rsa:4096 \
-nodes -sha256 -keyout certs/rch.hjn.key \  # 生成私钥,-nodes表示不加密
-addext "subjectAltName = DNS:rch.hjn.com" \  # 指定证书对应的域名
-x509 -days 365 -out certs/rch.hjn.crt  # 生成自签名证书(x509格式),有效期1年
[root@docker-ce ~]# ll certs/
总用量 8
-rw-r--r-- 1 root root 2114 10月 10 10:40 rch.hjn.crt  # 证书文件
-rw------- 1 root root 3272 10月 10 10:39 rch.hjn.key  # 私钥文件
  • 接下来就可以启动 registry 仓库了(让仓库用上刚做的 "加密身份证")
bash 复制代码
# 启动一个registry容器,作为私有仓库,配置https
[root@docker-ce ~]# docker run -d -p 443:443 --restart=always --name registry \
-v /opt/registry:/var/lib/registry \  # 把仓库数据挂载到本地/opt/registry,容器删了数据还在
-v /root/certs:/certs \  # 挂载证书目录到容器
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \  # 仓库监听443端口(https默认端口)
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/rch.hjn.crt \  # 指定证书路径
-e REGISTRY_HTTP_TLS_KEY=/certs/rch.hjn.key \  # 指定私钥路径
registry  # 使用官方registry镜像

# 确保docker配置里没有http的仓库设置,避免冲突
[root@docker-ce ~]# cat /etc/docker/daemon.json
{
    "registry-mirrors": ["https://docker.m.daocloud.io"]
}
[root@docker-ce ~]# systemctl restart docker  # 重启docker让配置生效
  • 这个时候我们还是无法上传镜像,原因是 docker 客户端没有 key 和证书(客户端不认识仓库的 "加密身份证",会觉得不安全),所以:
bash 复制代码
[root@docker-ce ~]# mkdir /etc/docker/certs.d/rch.hjn.com/ -p
[root@docker-ce ~]# cp ./certs/rch.hjn.crt /etc/docker/certs.d/rch.hjn.com/ca.crt
[root@docker-ce ~]# ll certs/
-rw-r--r-- 1 root root 2114 10月 10 10:40 rch.hjn.crt
-rw------- 1 root root 3272 10月 10 10:39 rch.hjn.key
[root@docker-ce ~]# ll /etc/docker/certs.d/rch.hjn.com/
-rw-r--r-- 1 root root 2114 10月 10 10:58 ca.crt
[root@docker-ce ~]# systemctl restart docker
  • 接下来可以测试上传了(现在加密通道通了,试试传个镜像)
bash 复制代码
#要在客户端做本地域名解析
[root@docker-ce ~]# cat /etc/hosts
192.168.2.50 rch.hjn.com
# 给busybox镜像打标签,格式:仓库域名/镜像名:标签(告诉docker要传到哪个仓库)
[root@docker-ce ~]# docker tag busybox:latest rch.hjn.com/busybox:latest
[root@docker-ce ~]# docker push rch.hjn.com/busybox:latest
The push refers to repository [rch.hjn.com/busybox]
80e840de630d: Pushed 
latest: digest: sha256:ebd0a6156be6cdc24c4bd1ad0e0fcb938cbdd5f8b86b15d18821f280902b6380 size: 527
# 用curl访问仓库API,查看仓库里的镜像(-k忽略证书验证,因为是自签名证书)
[root@docker-ce ~]# curl -k https://rch.hjn.com/v2/_catalog
{"repositories":["busybox","webserver"]}

5.5 为仓库建立登陆认证

仓库光加密还不够,就像快递包了,但谁都能拿也不行。得加个密码,只有登录的人才能上传下载。思路就是:用工具生成一个 "用户名密码表",让仓库启动时用上这个表,这样每次操作都得先输密码。

bash 复制代码
#安装htpasswd工具(用来生成密码文件)
[root@docker-ce ~]# yum install httpd-tools -y
#建立认证文件
[root@docker-ce ~]# mkdir auth
[root@docker-ce ~]# htpasswd -Bc auth/htpasswd rch	#-B 强制使用最安全加密方式,默认用md5加密
New password: 
Re-type new password: 
Adding password for user rch
[root@docker-ce ~]# cat auth/htpasswd 
rch:$2y$05$gCpyZhAO7u2ydVdsxTFihurjq7eROd3lH83xHQxJCftDTuQfSQMLS

#添加认证到registry容器中
[root@docker-ce ~]# docker run -d -p 443:443 --restart=always --name registry \
> -v /opt/registry:/var/lib/registry \  # 挂载数据目录(和之前一样)
> -v /root/certs:/certs \  # 挂载证书(和之前一样,保证加密)
> -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \  # 监听443端口(和之前一样)
> -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/rch.hjn.crt \  # 证书路径(和之前一样)
> -e REGISTRY_HTTP_TLS_KEY=/certs/rch.hjn.key \  # 密钥路径(和之前一样)
> -v /root/auth:/auth \		# 挂载认证文件目录(把账本给仓库)
> -e "REGISTRY_AUTH=htpasswd" \		# 启用htpasswd认证(告诉仓库用账本验证)
> -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \	# 认证提示信息(登录时显示的提示)
> -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \	# 认证文件路径(告诉仓库账本在哪)
> registry  # 用官方镜像
a213f6797af474f3b0c74a0db88e792592e7d9531c64e181fd0c46bc97881bce


# 未登录时访问仓库,提示需要认证
[root@docker-ce ~]# curl -k https://rch.hjn.com/v2/_catalog
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
# 登录仓库(用刚才创建的用户rch)
[root@docker-ce ~]# docker login rch.hjn.com
Username: rch
Password: 

WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/

Login Succeeded

当仓库开启认证后必须登陆仓库才能进行镜像上传(没登录就是不行,验证一下)

bash 复制代码
#未登陆情况下上传镜像
[root@docker-ce ~]# docker push  rch.hjn.com/busybox
Using default tag: latest
The push refers to repository [reg.timinglee.org/busybox]
d51af96cf93e: Preparing
no basic auth credentials

#未登陆情况下也不能下载
[root@docker-ce1 ~]# docker pull tch.hjn.com/busybox
Using default tag: latest
Error response from daemon: Head "https://rch.hjn.com/v2/busybox/manifests/latest": no basic auth credentials

5.6 构建企业级私有仓库(Harbor)

下载软件包地址

https://github.com/goharbor/harbor/releases

Registry 功能太简单,企业需要更强大的能力(网页管理、镜像扫描、细粒度权限等),此时用 Harbor(VMware 开源的企业级仓库)。

它提供了以下主要功能和特点:

  1. 基于角色的访问控制(RBAC):可以为不同的用户和用户组分配不同的权限,增强了安全性和管理的灵活性。
  2. 镜像复制:支持在不同的 Harbor 实例之间复制镜像,方便在多个数据中心或环境中分发镜像。
  3. 图形化用户界面(UI):提供了直观的 Web 界面,便于管理镜像仓库、项目、用户等。
  4. 审计日志:记录了对镜像仓库的各种操作,有助于追踪和审查活动。
  5. 垃圾回收:可以清理不再使用的镜像,节省存储空间。

5.6.1 部署harbor

Harbor 是 "全家桶" 式仓库,包含网页 UI、镜像存储、权限管理等组件,部署步骤:

  1. 下载 Harbor 离线安装包(内含所有必要镜像,无需额外拉取);
  2. 编辑配置文件 harbor.yml(设置域名、HTTPS 证书、管理员密码等);
  3. 运行安装脚本 install.sh,它会通过 Docker Compose 自动启动所有组件。
bash 复制代码
[root@docker-ce ~]# cd /opt/
[root@docker-ce opt]# ll
总用量 721484
drwx--x--x 4 root root        28 10月  9 10:30 containerd
-rw-r--r-- 1 root root 738797440 10月 10 11:45 harbor-offline-installer-v2.5.4.tgz
drwxr-xr-x 3 root root        20 10月 10 11:05 registry
[root@docker-ce opt]# tar zxf harbor-offline-installer-v2.5.4.tgz 
[root@docker-ce opt]# cd harbor/
[root@docker-ce harbor]# ll
总用量 725340
-rw-r--r-- 1 root root      3639  8月 28  2022 common.sh  # 通用脚本
-rw-r--r-- 1 root root 742709608  8月 28  2022 harbor.v2.5.4.tar.gz  # 包含所有组件的镜像包
-rw-r--r-- 1 root root      9917  8月 28  2022 harbor.yml.tmpl  # 配置文件模板(需要改)
-rwxr-xr-x 1 root root      2622  8月 28  2022 install.sh  # 安装脚本
-rw-r--r-- 1 root root     11347  8月 28  2022 LICENSE  # 许可证
-rwxr-xr-x 1 root root      1881  8月 28  2022 prepare  # 准备脚本

编辑配置文件

bash 复制代码
[root@docker-ce harbor]# cp harbor.yml.tmpl harbor.yml #复制模板为正式配置文件
[root@docker-ce harbor]# vim harbor.yml
  hostname: rch.hjn.com  # 仓库的域名(和之前的证书对应)
  certificate: /data/certs/rch.hjn.crt  # 证书路径(之前生成的证书放这)
  private_key: /data/certs/rch.hjn.key  # 密钥路径(之前生成的密钥放这)
  harbor_admin_password: 123  # 管理员密码(登录网页用的,自己设)
[root@docker-ce harbor]# mkdir /data
[root@docker-ce harbor]# cp -r /root/certs/ /data/
[root@docker-ce harbor]# tree /data/
/data/
└── certs
    ├── rch.hjn.crt
    └── rch.hjn.key
bash 复制代码
[root@docker harbor]# ./install.sh --help

Please set --with-notary 				#证书签名
Please set --with-trivy  				#安全扫描
Please set --with-chartmuseum if needs enable Chartmuseum in Harbor

[root@docker-ce harbor]# ./install.sh --with-chartmuseum
[root@docker-ce harbor]# docker compose up -d #启动所有组件(如果没自动启动的话)
WARN[0000] /opt/harbor/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion 
[+] Running 10/10
 ✔ Container harbor-log         Running                                                                          0.0s 
 ✔ Container registry           Running                                                                          0.0s 
 ✔ Container harbor-db          Running                                                                          0.0s 
 ✔ Container registryctl        Running                                                                          0.0s 
 ✔ Container chartmuseum        Running                                                                          0.0s 
 ✔ Container redis              Running                                                                          0.0s 
 ✔ Container harbor-core        Running                                                                          0.0s 
 ✔ Container harbor-jobservice  Running                                                                          0.0s 
 ✔ Container harbor-portal      Running                                                                          0.0s 
 ✔ Container nginx              Running                                                                          0.0s 

5.6.2 管理仓库(Harbor)

通过 Harbor 网页界面,可进行:

  • 镜像管理:上传、下载、删除镜像,给镜像打标签;
  • 项目与权限:创建项目(类似 "文件夹"),为不同用户分配 "只读""读写" 等权限;
  • 安全扫描:扫描镜像漏洞,提前发现风险;
  • 镜像复制:将镜像同步到其他 Harbor 仓库(如多数据中心同步)。

1.登陆

2.建立仓库项目

3.上传镜像

bash 复制代码
[root@docker-ce harbor]# docker login rch.hjn.com
Username: admin
Password: 

WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/

Login Succeeded
[root@docker-ce harbor]# docker tag busybox:latest rch.hjn.com/rch/busybox:latest
[root@docker-ce harbor]# docker push rch.hjn.com/rch/busybox:latest 
The push refers to repository [rch.hjn.com/rch/busybox]
80e840de630d: Layer already exists 
latest: digest: sha256:ebd0a6156be6cdc24c4bd1ad0e0fcb938cbdd5f8b86b15d18821f280902b6380 size: 527

4.查看上传的镜像

6.Docker 网络

容器不是孤立的,得 "联网"(比如两个容器互相通信,或者外面能访问容器里的网站)。Docker 提供了好几种网络模式,就像不同的联网方式。

docker 的镜像是很厉害,但网络功能相对简单点。

docker 安装后会自动创建 3 种网络:bridge(桥接)、host(主机)、none(无网络)

bash 复制代码
[root@docker harbor]# docker network ls  # 查看所有docker网络
NETWORK ID     NAME      DRIVER    SCOPE
2a93d6859680   bridge    bridge    local  # 默认桥接网络
4d81ddd9ed10   host      host      local  # 主机网络
8c8c95f16b68   none      null      local  # 无网络

6.1 docker原生网络

6.1.1 bridge

这是 Docker 默认网络模式,相当于给容器建 "内部网桥"。

  • 每个容器会被分配 "bridge 网段内的 IP"(如 172.17.0.*);
  • 容器间可通过内网 IP 通信;
  • 外部访问容器需 "端口映射"(如 docker run -p 8080:80 nginx,将容器 80 端口映射到主机 8080 端口,外部访问主机 8080 即访问容器 80)。

docker 安装时会创建一个叫 docker0 的 Linux 网桥,新容器会自动连到这个网桥上(就像新设备自动连家里的路由器)

  1. docker0 网桥
  • 相当于一个虚拟交换机
  • IP: 172.17.0.1,是容器的网关
  • 负责容器间的内部通信
  1. veth (Virtual Ethernet)
  • 虚拟网线,连接容器和网桥
  • 一端在容器内(eth0),一端在宿主机上连接到 docker0
  1. 容器网络接口
  • nginx: eth0: 172.17.0.2
  • ubuntu: eth0: 172.17.0.3
  • 都是从 172.17.0.0/16 网段自动分配的
  1. IP 转发
  • 宿主机要开启 net.ipv4.ip_forward = 1(允许转发数据包,不然容器上不了外网)
  • 让宿主机能够转发数据包,实现容器与外部通信

Bridge 模式就像在宿主机内部建了一个"公司内部网络",docker0 是"公司路由器",容器是"各个部门的电脑",veth 是"网线"。部门间可以自由通信,但要访问外网需要经过路由器 NAT 转换。

bash 复制代码
#容器网络(启动一个busybox容器,看它的网络)
[root@docker-ce2 ~]# docker run -it --name busybox busybox:latest  # 启动容器,用默认bridge网络
/ # ifconfig  # 查看容器内的网络
eth0      Link encap:Ethernet  HWaddr 56:29:C3:31:17:1B  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0  # docker0分配的IP
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:23 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:2968 (2.8 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  # 回环地址(自己访问自己)
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


#宿主机网络(看宿主机的网络,有docker0和veth)
[root@docker-ce2 ~]# ifconfig 
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500  # docker0网桥
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255  # 网关IP
        inet6 fe80::6096:7aff:fe7e:61d6  prefixlen 64  scopeid 0x20<link>
        ether 62:96:7a:7e:61:d6  txqueuelen 0  (Ethernet)
        RX packets 3  bytes 84 (84.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 21  bytes 3242 (3.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500  # 宿主机的物理网卡
        inet 192.168.2.51  netmask 255.255.255.0  broadcast 192.168.2.255
        inet6 fe80::20c:29ff:fec2:200f  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:c2:20:0f  txqueuelen 1000  (Ethernet)
        RX packets 128494  bytes 189628522 (180.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 26855  bytes 1642059 (1.5 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536  # 宿主机回环地址
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 56  bytes 5578 (5.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 56  bytes 5578 (5.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth47c093d: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500  # 虚拟网线的宿主机端(连docker0)
        inet6 fe80::2015:25ff:fe04:68cc  prefixlen 64  scopeid 0x20<link>
        ether 22:15:25:04:68:cc  txqueuelen 0  (Ethernet)
        RX packets 3  bytes 126 (126.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 36  bytes 5264 (5.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

6.1.2 host

容器直接使用主机网络栈,相当于 "容器与主机共用网卡"。

  • 容器端口直接对应主机端口(如容器用 80 端口,外部访问主机 80 即访问容器);
  • 优点:网络性能好,无需端口映射;缺点:容器与主机端口冲突风险高,隔离性弱。
  • nginx 容器:使用 Host 模式,直接共享宿主机的网络栈
  • IP 地址相同 :nginx 容器和宿主机 eth0 都是 172.25.0.11

"容器直接使用宿主机的网络,就像在宿主机上直接运行进程一样,简单高效但缺乏隔离。"

在图中,nginx 容器享受了 Host 模式的高性能,而 ubuntu 容器则保持了 Bridge 模式的安全隔离。

bash 复制代码
[root@docker-ce2 ~]# ifconfig 
#容器网络(启动一个用host网络的容器,看它的网络)
[root@docker-ce2 ~]# docker run -it --name busybox --rm --network host busybox:latest  # --network host指定用主机网络
/ # ifconfig  # 容器里的网络和宿主机完全一样
docker0   Link encap:Ethernet  HWaddr 62:96:7A:7E:61:D6  # 宿主机的docker0
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          inet6 addr: fe80::6096:7aff:fe7e:61d6/64 Scope:Link
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:3 errors:0 dropped:0 overruns:0 frame:0
          TX packets:21 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:84 (84.0 B)  TX bytes:3242 (3.1 KiB)

ens160    Link encap:Ethernet  HWaddr 00:0C:29:C2:20:0F  # 宿主机的物理网卡
          inet addr:192.168.2.51  Bcast:192.168.2.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fec2:200f/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:128716 errors:0 dropped:0 overruns:0 frame:0
          TX packets:27016 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:189646454 (180.8 MiB)  TX bytes:1659601 (1.5 MiB)

lo        Link encap:Local Loopback  # 回环地址
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:56 errors:0 dropped:0 overruns:0 frame:0
          TX packets:56 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:5578 (5.4 KiB)  TX bytes:5578 (5.4 KiB)

#宿主机网络(和容器里的完全一样,证明共用网络)
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::6096:7aff:fe7e:61d6  prefixlen 64  scopeid 0x20<link>
        ether 62:96:7a:7e:61:d6  txqueuelen 0  (Ethernet)
        RX packets 3  bytes 84 (84.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 21  bytes 3242 (3.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.2.51  netmask 255.255.255.0  broadcast 192.168.2.255
        inet6 fe80::20c:29ff:fec2:200f  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:c2:20:0f  txqueuelen 1000  (Ethernet)
        RX packets 128727  bytes 189647332 (180.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 27025  bytes 1661995 (1.5 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 56  bytes 5578 (5.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 56  bytes 5578 (5.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

6.1.3 none

容器完全无网络配置 ,只有回环地址(127.0.0.1),相当于 "断网容器"。

  • 适合完全不需要联网的场景(如纯本地计算的容器);
  • 容器间、容器与外部均无法通信,安全性极高但灵活性差。
bash 复制代码
[root@docker-ce2 ~]# docker run -it --name busybox --rm --network none  busybox:latest  # --network none指定无网络
/ # ifconfig  # 只有回环地址,没有其他网卡
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
  • 只有 lo 回环接口127.0.0.1
  • 无 eth0 或其他网络接口
  • 无法与任何其他容器通信
  • 无法与宿主机通信(除了通过 volumes 等非网络方式)
  • 完全无法访问外网

"就像把应用关在一个完全没有门窗的房间里 - 绝对安全,但也完全与世隔绝。只能通过'传送带'(volume)来输入输出数据。"

这种模式虽然使用场景有限,但在需要极致安全的环境中非常重要!

三种模式对比总结

特性 None 模式 Host 模式 Bridge 模式
网络性能 无网络 ⭐⭐⭐⭐⭐ ⭐⭐⭐
安全性 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐
隔离性 完全隔离 无隔离 网络隔离
容器间通信 不可能 直接通信 通过网桥
外部访问 不可能 直接暴露 需要端口映射
使用场景 安全任务 高性能应用 大多数应用

6.2 docker自定义网络(容器间通信)

自定义网络模式,docker 提供了三种自定义网络驱动:bridge、overlay、macvlan。

bridge 驱动类似默认的 bridge 网络,但更好用(比如支持用名字通信);overlay 和 macvlan 是用于不同主机的容器通信。

建议用自定义网络来控制哪些容器能互相通信,还能自动把容器名字转成 IP(不用记 IP,方便)。

6.2.1 自定义桥接网络---带DNS解析,默认的不带(同一个自定义网络)

让主机能够通过容器名字识别容器的IP

bash 复制代码
[root@docker-ce2 ~]# docker network create my_net1  # 创建一个自定义bridge网络(默认参数)
78b2135cdaa2edc0c569fc7a862b3cb61080120ade9238e04c7dc9709a1d94f0
[root@docker-ce2 ~]# docker network create my_net2 --subnet 192.168.0.0/24 --gateway 192.168.0.100  # 创建指定网段和网关的自定义网络
60c80845c57e7ad340f58043cdea88173c0987afc400e451d940660c8a478ea9
[root@docker-ce2 ~]# docker network inspect my_net2  # 查看my_net2的详细信息
[
    {
        "Name": "my_net2",
        "Id": "60c80845c57e7ad340f58043cdea88173c0987afc400e451d940660c8a478ea9",
        "Created": "2025-10-14T00:04:08.960745999+08:00",
        "Scope": "local",
        "Driver": "bridge",  # 驱动是bridge
        "EnableIPv4": true,
        "EnableIPv6": false,
        "IPAM": {  # IP地址管理,就是我们指定的网段和网关
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.0.0/24",  # 网段
                    "Gateway": "192.168.0.100"  # 网关
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},  # 目前还没有容器加入这个网络
        "Options": {},
        "Labels": {}
    }
]
#启动容器看看效果(在my_net2网络里启动容器)
[root@docker-ce2 ~]# docker run -it --name busybox --rm --network my_net2  busybox:latest  # --network指定用my_net2网络
/ # ifconfig  # 容器IP是192.168.0.1,在我们指定的网段里
eth0      Link encap:Ethernet  HWaddr FE:DA:3E:E7:D9:D0  
          inet addr:192.168.0.1  Bcast:192.168.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:24 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:3132 (3.0 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
#在开启一个终端,启动另一个容器(也用my_net2网络),看是否ping通
[root@docker-ce2 ~]# docker run -it --name test1 --rm --network my_net2 busybox:latest  # 名字叫test1
/ # 
#回到名为busybox的容器,发现可以ping通test1这个容器(用名字就能ping通,因为有DNS解析)
/ # ping test1
PING test1 (192.168.0.2): 56 data bytes
64 bytes from 192.168.0.2: seq=0 ttl=64 time=0.141 ms  # 通了!
64 bytes from 192.168.0.2: seq=1 ttl=64 time=0.085 ms

6.2.2 如何让不同的自定义网络互通?--不同网络栈

默认不同自定义网络的容器是隔离的,就像两个不同的 WiFi,连 A WiFi 的手机不能连 B WiFi 的设备。要让它们通,就把一个设备同时连两个 WiFi。

bash 复制代码
[root@docker-ce2 ~]# docker network create mynet1
041ef375bb6fc2681b3f486971b330a3d4e7c52dd1af0621ce1cf03b9218745b
[root@docker-ce2 ~]# docker network create mynet2
4f9f53b81ef48c357e560b9a9e239921ae14219c47014379e1cc3c1de1dd05af
[root@docker-ce2 ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
00a3c1c3d1b9   bridge    bridge    local
a93a66fe4326   host      host      local
78b2135cdaa2   my_net1   bridge    local
60c80845c57e   my_net2   bridge    local
041ef375bb6f   mynet1    bridge    local
4f9f53b81ef4   mynet2    bridge    local
3d53bea42934   none      null      local
bash 复制代码
[root@docker-ce2 ~]# docker run -it --name test --rm --network mynet1 busybox:latest  # 在mynet1启动test容器
/ # ifconfig  # IP是172.19.0.2(mynet1的网段)
eth0      Link encap:Ethernet  HWaddr 3E:82:A4:8A:3E:9A  
          inet addr:172.19.0.2  Bcast:172.19.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:37 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:5334 (5.2 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
#再开一个容器,使用mynet2网络(名字叫test1)
[root@docker-ce2 ~]# docker run -it --name test1 --rm --network mynet2  busybox:latest 
/ # ifconfig  # IP是172.20.0.2(mynet2的网段)
eth0      Link encap:Ethernet  HWaddr AA:24:CD:8C:4B:EF  
          inet addr:172.20.0.2  Bcast:172.20.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:26 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:3512 (3.4 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
#发现容器之间无法互通,因为不在一个网络(网段不同)
/ # ping 172.19.0.2
PING 172.19.0.2 (172.19.0.2): 56 data bytes      # 不通

想让不同网段之间互通,可以在上面test1容器中加入网络test的网络栈mynet1

bash 复制代码
#ctrl+p+q退出test1容器(不关闭,后台运行)
[root@docker-ce2 ~]# docker network connect mynet1 test1  # 把test1容器加入mynet1网络
[root@docker-ce2 ~]# docker attach test1  # 重新进入test1容器
/ # ping 172.19.0.2  # 现在能ping通test容器了
PING 172.19.0.2 (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.113 ms  # 通了!
64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.109 ms
64 bytes from 172.19.0.2: seq=2 ttl=64 time=0.099 ms

6.2.3 joined容器网络--两个容器同一个网络栈

两个容器共用一个网络,就像两个程序跑在同一台机器上,用同一个 IP 和端口。

joined 容器模式和自定义 Bridge 网络区别

特性 joined 容器 自定义 Bridge 网络
网络命名空间 共享同一个 每个容器独立
IP 地址 相同 IP 地址 不同 IP 地址
端口管理 共享端口空间,可能冲突 各自独立的端口空间
网络隔离 无隔离,完全共享 有隔离,通过网桥连接
通信方式 通过 localhost 通过容器名或 IP
DNS 解析 无 DNS,只有 localhost 内置 DNS,容器名解析
  • joined 容器:像是"连体双胞胎" - 共享所有网络器官
  • 自定义 Bridge:像是"邻居" - 各自有独立房子,但有专用通道连接
bash 复制代码
#启动一个nginx容器,查看它的IP(默认bridge网络)
[root@docker-ce2 ~]# docker run -d --rm --name webserver nginx:1.24  # 名字叫webserver,后台运行
4be904a62529a6cc8e7988a9422d12a0a0e684b069cf3525117c7d0b5926ddff
[root@docker-ce2 ~]# docker inspect webserver  # 查看webserver的IP
 "Gateway": "172.17.0.1",
 "IPAddress": "172.17.0.3",  # IP是172.17.0.3
#再开一个终端运行centos容器,用joined模式连到webserver
[root@docker-ce2 ~]# docker run -it --rm --name centOS --network container:webserver centos:7  # --network container:webserver表示和webserver共享网络
[root@4be904a62529 /]# yum install net-tools -y  # 安装ifconfig工具
[root@4be904a62529 /]# ifconfig  # 查看centos容器的IP,和webserver一样是172.17.0.3
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.3  netmask 255.255.0.0  broadcast 172.17.255.255
        ether aa:9c:db:e1:4d:be  txqueuelen 0  (Ethernet)
        RX packets 4002  bytes 64045529 (61.0 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2734  bytes 152242 (148.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
#centos容器没装nginx,却可以访问nginx(因为共享网络,相当于访问自己)
[root@4be904a62529 /]# curl 127.0.0.1  # 访问本地,其实是访问webserver容器的nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

6.2.4 jioned网络示例

用 joined 模式让 phpmyadmin(管理 mysql 的工具)和 mysql 容器共享网络,这样 phpmyadmin 不用记 mysql 的 IP,直接访问localhost就行。

bash 复制代码
[root@docker-ce2 ~]# docker pull mysql:8.0  # 拉取mysql镜像
[root@docker-ce2 ~]# docker load -i phpmyadmin-latest.tar.gz  # 加载phpmyadmin镜像(本地文件)
[root@docker-ce2 ~]# docker images  # 查看镜像
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
mysql        8.0       94753e67a0a9   2 weeks ago     780MB
phpmyadmin   latest    2b39e17532a1   14 months ago   562MB

[root@docker-ce2 ~]# docker run -d --name mysqladmin --network my_net1 \
> -e PMA_ARBITRARY=1 \	# 允许在web页面手动输入数据库地址和端口
> -p 80:80 phpmyadmin:latest	# 端口映射,主机80端口对应容器80(phpmyadmin默认用80)

[root@docker-ce2 ~]# docker run -d --name mysql \
> -e MYSQL_ROOT_PASSWORD='123' \	# 设定mysql的root密码为123
> --network container:mysqladmin \	# 和mysqladmin共享网络(joined模式)
> mysql:8.0  # 启动mysql容器
  • 开启的phpmyadmin容器中是没有数据库的

  • 这里填写的localhost:3306是因为 mysql 容器和 phpmyadmin 容器公用一个网络栈(mysql 在 3306 端口,phpmyadmin 访问localhost:3306就等于访问 mysql 容器)

6.3 容器内外网的访问

6.3.1 容器访问外网

容器要上外网(比如 ping 百度),靠的是宿主机的 NAT 转换(就像家里的路由器,多台设备共用一个公网 IP 上网)。

Docker 容器通过 NAT 方式访问外部网络的工作原理

bash 复制代码
[root@docker-ce2 ~]# iptables -t nat -nL  # 查看nat表的iptables规则(控制网络地址转换)
Chain PREROUTING (policy ACCEPT) # 路由之前:做DNAT(把外部地址转成内部地址)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            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  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)	# 路由之后:做SNAT(把内部地址转成外部地址,容器上网靠这个)
target     prot opt source               destination         
MASQUERADE  all  --  172.20.0.0/16        0.0.0.0/0           # 172.20.0.0网段的容器上网,用宿主机IP
MASQUERADE  all  --  172.19.0.0/16        0.0.0.0/0           # 172.19.0.0网段的容器上网,用宿主机IP
MASQUERADE  all  --  192.168.0.0/24       0.0.0.0/0           # 192.168.0.0网段的容器上网,用宿主机IP
MASQUERADE  all  --  172.18.0.0/16        0.0.0.0/0           
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           # 默认bridge网段的容器上网,用宿主机IP

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.18.0.2:80
[root@docker-ce2 ~]# docker run -it --name busybox busybox:latest  # 启动容器
/ # ping www.baidu.com  # 能ping通百度,证明容器能上外网
PING www.baidu.com (183.2.172.177): 56 data bytes
64 bytes from 183.2.172.177: seq=0 ttl=127 time=36.166 ms
64 bytes from 183.2.172.177: seq=1 ttl=127 time=35.613 ms

6.3.1 外网访问容器内网

外面的设备(比如自己的电脑)要访问容器里的服务(比如 nginx),需要端口映射把主机的端口和容器的端口连起来。

bash 复制代码
#通过docker-proxy对数据包进行内转
[root@docker-ce2 ~]# docker run -d --name webserver -p 80:80 nginx:1.24 
[root@docker-ce2 ~]# ps -aux | grep docker
root       34294  0.1  2.6 2129408 106136 ?      Ssl  10月13   0:19 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root       39324  0.0  0.1 1745216 5932 ?        Sl   01:57   0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.0.2 -container-port 80 -use-listen-fd
root       39332  0.0  0.1 1671484 6964 ?        Sl   01:57   0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 80 -container-ip 172.17.0.2 -container-port 80 -use-listen-fd
root       39423  0.0  0.7 1775188 30900 pts/3   Sl+  02:03   0:00 docker run -it --name asd busybox:latest
root       39504  0.0  0.0 221680  2340 pts/2    S+   02:05   0:00 grep --color=auto docker

#通过dnat策略来完成浏览内转
[root@docker-ce2 ~]# iptables -t nat -nL
Chain DOCKER (2 references)
target     prot opt source               destination                 
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.2:80 #

内网访问外网只有一条路,外网访问内网有两条路,双通道

6.4 docker跨主机网络

在生产环境中,我们的容器不可能都在同一个系统中,所以需要容器具备跨主机通信的能力

  • 跨主机网络解决方案
    • docker原生的overlay(三层)和macvlan(二层)
    • 第三方的flannel、weave、calico
  • 众多网络方案是如何与docker集成在一起的
    • libnetwork docker容器网络库
    • CNM (Container Network Model)这个模型对容器网络进行了抽象

6.3.1 CNM (Container Network Model

CNM分三类组件

Sandbox:容器网络栈,包含容器接口、dns、路由表。(namespace)

Endpoint:作用是将sandbox接入network (veth pair)

Network:包含一组endpoint,同一network的endpoint可以通信

6.3.2 macvlan网络方式实现跨主机通信

macvlan网络方式

Linux kernel提供的一种网卡虚拟化技术。

无需Linux bridge,直接使用物理接口,性能极好

容器的接口直接与主机网卡连接,无需NAT或端口映射。macvlan会独占主机网卡,但可以使用vlan子接口实现多macvlan网络

vlan可以将物理二层网络划分为4094个逻辑网络,彼此隔离,vlan id取值为1~4094

macvlan网络间的隔离和连通

macvlan网络在二层上是隔离的,所以不同macvlan网络的容器是不能通信的

可以在三层上通过网关将macvlan网络连通起来

docker本身不做任何限制,像传统vlan网络那样管理即可

实现方法如下

1.在两台docker主机上各添加一块仅主机网卡,打开网卡混杂模式:

bash 复制代码
[root@docker-ce harbor]# ip link set ens192 promisc on  # 打开ens192网卡的混杂模式
[root@docker-ce harbor]# ip link set up ens192  # 启用网卡
[root@docker-ce harbor]# ifconfig ens192  # 查看网卡,有PROMISC标志,说明成功
ens192: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500  # 有PROMISC
        ether 00:0c:29:c9:1d:4b  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

2.添加macvlan网络

bash 复制代码
[root@docker-ce harbor]# docker network create \
-d macvlan \		# -d指定驱动为macvlan
--subnet 1.1.1.0/24 \	# 容器的网段(两台主机要一样)
--gateway 1.1.1.1 \ # 网关(两台主机要一样)
-o parent=ens192 macvlan1  # -o parent指定用哪块物理网卡,网络名字叫macvlan1

3.测试

bash 复制代码
#在一台主机上,启动容器(指定IP为1.1.1.10)
[root@docker-ce harbor]# docker run -it --name busybox --network macvlan1 --ip 1.1.1.10 --rm busybox:latest 
/ # ifconfig  # 容器IP是1.1.1.10
eth0      Link encap:Ethernet  HWaddr EE:17:5A:C6:4D:C5  
          inet addr:1.1.1.10  Bcast:1.1.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:126 (126.0 B)
#在另一台主机上也启动容器(指定IP为1.1.1.11)
[root@docker-ce2 ~]# docker run -it --name busybox --network macvlan1 --ip 1.1.1.11 --rm busybox:latest
/ # ifconfig  # 容器IP是1.1.1.11
eth0      Link encap:Ethernet  HWaddr 4E:80:DC:0A:8B:08  
          inet addr:1.1.1.11  Bcast:1.1.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:126 (126.0 B)

#ping测试(不同主机的容器能ping通)
/ # ping 1.1.1.11
PING 1.1.1.11 (1.1.1.11): 56 data bytes
64 bytes from 1.1.1.11: seq=0 ttl=64 time=1.026 ms  # 通了!跨主机通信成功
64 bytes from 1.1.1.11: seq=1 ttl=64 time=0.372 ms
64 bytes from 1.1.1.11: seq=2 ttl=64 time=0.352 ms

7.Docker 数据卷管理及优化

Docker 容器里的文件默认存在自身文件系统中,但容器删除后数据会丢失,且多容器共享数据也不方便。数据卷(Volume) 就是为解决这些问题而生 ------ 让容器数据能持久化到宿主机,还能方便地共享、备份。

这样可以实现以下几个重要的目的:

  • 数据持久化:即使容器被删除或重新创建,数据卷中的数据仍然存在,不会丢失。
  • 数据共享:多个容器可以同时挂载同一个数据卷,实现数据的共享和交互。
  • 独立于容器生命周期:数据卷的生命周期独立于容器,不受容器的启动、停止和删除的影响。

7.1 为什么要用数据卷

数据卷的核心作用是 让容器数据 "脱离" 容器本身,实现持久保存、跨容器共享

docker分层文件系统

  • 性能差
  • 生命周期与容器相同

docker数据卷

  • mount到主机中,绕开分层文件系统
  • 和主机磁盘性能相同,容器删除后依然保留
  • 仅限本地磁盘,不能随容器迁移

docker提供了两种卷:

  • bind mount
  • docker managed volume

7.2 bind mount 数据卷

bind mount 是最直接的方式:把宿主机已存在的目录 / 文件,直接 "挂" 到容器内的某个路径。容器和宿主机共用该路径内容,两边修改彼此可见。

  • 是将主机上的目录或文件mount到容器里。
  • 使用直观高效,易于理解。
  • 使用 -v 选项指定路径,格式 :
  • -v选项指定的路径,如果不存在,挂载时会自动创建

示例:

bash 复制代码
[root@docker-ce2 ~]# docker run -d --name mysql -p 3306:3306 -v /data:/var/lib/mysql -v /var/run/mysqld:/var/run/mysqld/ -e MYSQL_ROOT_PASSWORD="123" mysql:8.0 
[root@docker-ce2 ~]# mysql -uroot -p123 --socket=/var/run/mysqld/mysqld.sock
mysql> create database rch;
Query OK, 1 row affected (0.00 sec)

#另开一个主机测试
[root@docker-ce ~]# mysql -uroot -p123 -h 192.168.2.51
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| rch                |
| sys                |
+--------------------+

7.3 docker managed 数据卷

若不想手动在宿主机建目录,想让 Docker 自动管理数据卷的存储位置,就用 docker managed 数据卷 。Docker 会将数据卷存放在自身目录下(一般是 /var/lib/docker/volumes/),用户无需指定宿主机具体路径,Docker 自动处理。

bash 复制代码
[root@docker-ce2 volumes]# ll
总用量 24
brw------- 1 root root 253, 0 10月 13 19:10 backingFsBlockDev
-rw------- 1 root root  32768 10月 14 21:27 metadata.db
drwx-----x 3 root root     19 10月 14 11:15 rchvolume
[root@docker-ce2 volumes]# docker run -d --name mysql -e MYSQL_ROOT_PASSWORD='123' mysql:8.0 
bd615280f00b3484972c739acb451b846af5e31967c9052513ad95bf7b4dcc74
[root@docker-ce2 volumes]# ll
总用量 24
drwx-----x 3 root root     19 10月 14 21:27 9a0c1ef777f074b5ad02cb886f98016fd7d96a133c17a6d8470726d83b5ef5f9
brw------- 1 root root 253, 0 10月 13 19:10 backingFsBlockDev
-rw------- 1 root root  32768 10月 14 21:27 metadata.db
drwx-----x 3 root root     19 10月 14 11:15 rchvolume
[root@docker-ce2 volumes]# touch 9a0c1ef777f074b5ad02cb886f98016fd7d96a133c17a6d8470726d83b5ef5f9/_data/rchfile
[root@docker-ce2 volumes]# docker exec -it mysql bash
bash-5.1# ls- l /var/lib/mysql
bash: ls-: command not found
bash-5.1# ls -l /var/lib/mysql

-rw-r--r-- 1 root  root         0 Oct 14 13:28  rchfile

清理未使用的 Docker 数据卷

复制代码
[root@docker ~]# docker volume prune

!NOTE

  1. 在执行 docker volume prune 命令之前,请确保你确实不再需要这些数据卷中的数据,因为该操作是不可逆的,一旦删除数据将无法恢复。
  2. 如果有重要的数据存储在数据卷中,建议先进行备份,或者确保数据已经被妥善保存到其他地方。

建立数据卷

复制代码
[root@docker ~]# docker volume create  leevol1
[root@docker ~]# ls -l  /var/lib/docker/volumes/leevol1/_data/

查看卷

复制代码
[root@docker ~]# docker volume ls
DRIVER    VOLUME NAME
local     leevol1

使用建立的数据卷

bash 复制代码
[root@docker-ce2 volumes]# docker run -d --name nginx -p 80:80 -v rchvolume:/usr/share/nginx/html/ nginx:1.24 
e6c7b561741c70cef18abca3df0dc9cdea2feaf7c580c8b9e41c0bbab91c6a0d
[root@docker-ce2 volumes]# cd rchvolume/_data/
[root@docker-ce2 _data]# ll
总用量 8
-rw-r--r-- 1 root root 497  4月 11  2023 50x.html
-rw-r--r-- 1 root root   4 10月 14 11:25 index.html
[root@docker-ce2 _data]# echo rch > index.html 
[root@docker-ce2 _data]# curl 192.168.2.51
rch

7.4 数据卷容器(Data Volume Container)

若多个容器需共享一批数据,或需要 "数据中心" 管理数据卷,可使用数据卷容器:专门建一个容器挂载数据卷,其他容器通过该容器挂载数据卷(相当于 "借用" 此容器的卷)。

bash 复制代码
[root@docker-ce2 ~]# docker run -d --name datavol \
> -v /rch/:/rch \
> -v /hjn:/hjn \
> busybox:latest
baa44cd20c30aef04d913fc9278911877895a2fe176485a4fe8664289ca969b8
[root@docker-ce2 ~]# docker run -it --name test --volumes-from datavol busybox:latest 
/ # ls
bin    dev    etc    hjn    home   lib    lib64  proc   rch    root   sys    tmp    usr    var

7.5 bind mount 数据卷和docker managed 数据卷的对比

简单总结:

  • 若要把宿主机某配置文件(如 /etc/nginx/nginx.conf)直接给容器用,选 bind mount
  • 若只需容器数据持久化,不关心数据在宿主机的存储位置,选 docker managed

7.6 备份与迁移数据卷

数据卷存着重要数据,必须备份或迁移到其他服务器。核心思路是:把数据卷内容拷贝出来(备份),或把备份内容导入新数据卷(迁移)

备份数据卷

bash 复制代码
#建立容器并指定使用卷到要备份的容器
[root@docker ~]# docker run --volumes-from datavol  \
-v `pwd`:/backup busybox \					#把当前目录挂在到容器中用于和容器交互保存要备份的容器
tar zcf /backup/data1.tar.gz  /data1		#备份数据到本地

还原数据

bash 复制代码
 docker run  -it --name test -v leevol1:/data1 -v `pwd`:/backup busybox /bin/sh -c "tar zxf /backup/data1.tar.gz;/bin/sh"
/ # ls
backup  data1   etc     lib     proc    sys     usr
bin     dev     home    lib64   root    tmp     var
/ # cd data1/			#查看数据迁移情况
/data1 # ls
index.html  leefile1

8.Docker 的安全优化

Docker 的安全优化核心围绕 "防止资源被滥用""强化容器隔离与权限管控" 展开:

Docker容器的安全性,很大程度上依赖于Linux系统自身

评估Docker的安全性时,主要考虑以下几个方面:

  • Linux内核的命名空间机制提供的容器隔离安全
  • Linux控制组机制对容器资源的控制能力安全。
  • Linux内核的能力机制所带来的操作权限安全
  • Docker程序(特别是服务端)本身的抗攻击性。
  • 其他安全增强机制对容器安全性的影响
bash 复制代码
#在rhel9中默认使用cgroup-v2 但是cgroup-v2中不利于观察docker的资源限制情况,所以推荐使用cgroup-v1
[root@docker ~]#  grubby --update-kernel=/boot/vmlinuz-$(uname -r) \
--args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"

1 命名空间隔离的安全

Docker 默认用 Linux 命名空间(Namespace) 隔离容器(如进程、网络、文件系统各自独立)

bash 复制代码
#容器
[root@docker-ce2 ~]# docker run -it --name test --rm busybox:latest 
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 sh
    8 root      0:00 ps


#宿主机
[root@docker-ce2 ~]# ps
    PID TTY          TIME CMD
  69447 pts/1    00:00:00 bash
  69550 pts/1    00:00:00 ps
[root@docker-ce2 ~]# docker inspect test | less	#找到容器进程的PID
"Pid": 69405,
[root@docker-ce2 ~]# cd /proc/69405/ns/		#查看该进程的namespace
[root@docker-ce2 ns]# ls
cgroup  ipc  mnt  net  pid  pid_for_children  time  time_for_children  user  uts
  • UTS: 主机名隔离
  • IPC: 进程间通信隔离
  • Net: 网络栈隔离
  • Mnt: 文件系统挂载点隔离
  • User: 用户和用户组隔离
  • Pid: 进程ID隔离
  • Cgroup: 控制组隔离

2 控制组资源控制的安全

  • 当docker run启动一个容器时,Docker将在后台为容器创建一个独立的控制组策略集合。
  • Linux Cgroups提供了很多有用的特性,确保各容器可以公平地分享主机的内存、CPU、磁盘IO等资源。
  • 确保当发生在容器内的资源压力不会影响到本地主机系统和其他容器,它在防止拒绝服务攻击(DDoS)方面必不可少
bash 复制代码
[root@docker ~]# docker run  -it --name test busybox			#内存资源默认没有被隔离
/ # free -m
              total        used        free      shared  buff/cache   available
Mem:           3627         648         516          16        2463        2678
Swap:          2063           1        2062
/ # exit
[root@docker ~]# free  -m
               total        used        free      shared  buff/cache   available
Mem:            3627         907         557          15        2463        2719
Swap:           2062           1        2061

3 内核能力机制

  • 能力机制(Capability)是Linux内核一个强大的特性,可以提供细粒度的权限访问控制。
  • 大部分情况下,容器并不需要"真正的"root权限,容器只需要少数的能力即可。
  • 默认情况下,Docker采用"白名单"机制,禁用"必需功能"之外的其他权限。

4 Docker服务端防护

  • 使用Docker容器的核心是Docker服务端,确保只有可信的用户才能访问到Docker服务。
  • 将容器的root用户映射到本地主机上的非root用户,减轻容器和主机之间因权限提升而引起的安全问题。
  • 允许Docker 服务端在非root权限下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。这些子进程只允许在特定范围内进行操作。
bash 复制代码
[root@docker ~]# ls -ld /var/lib/docker/   #默认docker是用root用户控制资源的
drwx--x--- 12 root root 171  8月 20 13:21 /var/lib/docker/

8.1 Docker的资源限制

Docker 借助 Linux 的 Cgroups(控制组) 技术,能为每个容器设定 CPU、内存、磁盘 IO 的使用上限,避免单个容器 "过度抢占资源" 拖垮宿主机。

Linux Cgroups 的全称是 Linux Control Group。

  • 是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。
  • 对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。

Linux Cgroups 给用户暴露出来的操作接口是文件系统

  • 它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
  • 执行此命令查看:mount -t cgroup
bash 复制代码
[root@docker-ce2 ~]# mount -t cgroup	##在rhel9中默认使用cgroup2
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/misc type cgroup (rw,nosuid,nodev,noexec,relatime,misc)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
[root@docker-ce2 ~]# cd /sys/fs/cgroup/
[root@docker-ce2 cgroup]# ls
blkio  cpuacct      cpuset   freezer  memory  net_cls           net_prio    pids  systemd
cpu    cpu,cpuacct  devices  hugetlb  misc    net_cls,net_prio  perf_event  rdma
  • 在 /sys/fs/cgroup 下面有很多诸如 cpuset、cpu、 memory 这样的子目录,也叫子系统。
  • 在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录)。
  • 控制组下面的资源文件里填上什么值,就靠用户执行 docker run 时的参数指定。

8.1.1 限制CPU使用

CPU 是核心资源,若不限制,某个容器里的程序可能把宿主机 CPU 占满,导致其他服务瘫痪。Docker 有两种限制方式:

精确控制使用率

通过 --cpu-period(统计周期,单位微秒,如 100000 代表 100 毫秒)和 --cpu-quota(周期内最多使用的 CPU 时间,如 20000 代表 20 毫秒),可将 CPU 使用率限制为 20000/100000 = 20%

bash 复制代码
#容器内
[root@docker-ce2 ~]# docker run -it --rm --name test --cpu-period 100000 --cpu-quota 20000 ubuntu:latest
root@b86c24e1fc7d:/# dd if=/dev/zero of=/dev/null &
[1] 9
root@b86c24e1fc7d:/# top
top - 14:58:32 up 45 min,  0 user,  load average: 0.06, 0.13, 0.06
Tasks:   3 total,   2 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.7 us,  2.3 sy,  0.0 ni, 96.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st 
MiB Mem :   3872.4 total,   1490.2 free,   1567.1 used,   1061.3 buff/cache     
MiB Swap:   4096.0 total,   4096.0 free,      0.0 used.   2305.3 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                 
      9 root      20   0    2736   1104   1016 R  20.0   0.0   0:34.63 dd   
      
#宿主机
[root@docker-ce2 ~]# top
top - 23:01:47 up 48 min,  4 users,  load average: 0.50, 0.33, 0.15
Tasks: 292 total,   2 running, 290 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.9 us,  2.3 sy,  0.0 ni, 96.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3872.4 total,   1493.0 free,   1564.1 used,   1061.5 buff/cache
MiB Swap:   4096.0 total,   4096.0 free,      0.0 used.   2308.3 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                 
   3329 root      20   0    2736   1104   1016 R  19.9   0.0   1:13.65 dd   
#发现cpu使用率被限制在了20%

#在cgroup中查看docker的资源限制
[root@docker-ce2 ~]# cd /sys/fs/cgroup/cpu/docker/b86c24e1fc7dbcf4d1e529e4edf7aebd7d7c1a174c1152e8a9d2b6014f18c62a/
[root@docker-ce2 b86c24e1fc7dbcf4d1e529e4edf7aebd7d7c1a174c1152e8a9d2b6014f18c62a]# ls
cgroup.clone_children  cpuacct.usage_all          cpuacct.usage_sys   cpu.cfs_quota_us  notify_on_release
cgroup.procs           cpuacct.usage_percpu       cpuacct.usage_user  cpu.idle          tasks
cpuacct.stat           cpuacct.usage_percpu_sys   cpu.cfs_burst_us    cpu.shares
cpuacct.usage          cpuacct.usage_percpu_user  cpu.cfs_period_us   cpu.stat
[root@docker-ce2 b86c24e1fc7dbcf4d1e529e4edf7aebd7d7c1a174c1152e8a9d2b6014f18c62a]# cat cpu.cfs_period_us
100000
[root@docker-ce2 b86c24e1fc7dbcf4d1e529e4edf7aebd7d7c1a174c1152e8a9d2b6014f18c62a]# cat cpu.cfs_quota_us
20000

调整争抢优先级

--cpu-shares 设置权重(默认 1024)。多个容器抢 CPU 时,权重高的能分到更多资源。比如容器 A 权重 512、容器 B 权重 1024,则 B 抢到的 CPU 是 A 的 2 倍。

bash 复制代码
#关闭cpu的核心,当cpu都不空闲下才会出现争抢的情况,为了实验效果我们可以关闭一个cpu核心
[root@docker-ce2 cpu0]# cd /sys/devices/system/cpu/cpu1 #cpu0默认在线
[root@docker-ce2 cpu1]# echo 0 > online 
[root@docker-ce2 cpu1]# cat /proc/cpuinfo 
cpu cores	: 1			#cpu核心数为1

#开启容器时,如果指定了CPU使用优先级,那么设定文件为
cat /sys/fs/cgroup/cpu/docker/容器 ID/cpu.shares
例如:
[root@docker-ce2 ~]# cat /sys/fs/cgroup/cpu/docker/8bf8a6878d15e007c8566aa9c84e6ffc1cb6bd83cc3b92ce2df3edf630a09d4f/cpu.shares 
100

测试:

bash 复制代码
#开启容器并限制资源,设定cpu优先级为100,最大为1024,值越大优先级越高
[root@docker-ce2 ~]# docker run -it --rm --name test --cpu-shares 100  ubuntu:latest
root@8bf8a6878d15:/# dd if=/dev/zero of=/dev/null &
[1] 8
root@8bf8a6878d15:/# top
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                 
      8 root      20   0    2736   1060    972 R  91.7   0.0   0:13.90 dd        
#因为没有别的进程使用CPU,所以无限制


#开启另外一个容器不限制cpu的优先级--发现不设定优先级,默认值为最大1024
[root@docker-ce2 ~]# docker run -it --rm --name test1 ubuntu:latest
[root@docker-ce2 /]# cd /sys/fs/cgroup/cpu/docker/da02eea728e508231d997661e347a3daf953d906c8ef19502e80f7e38c1c0b2a/
[root@docker-ce2 da02eea728e508231d997661e347a3daf953d906c8ef19502e80f7e38c1c0b2a]# cat cpu.shares 
1024

#在这个容器也执行dd命令,CPU使用率如下所示,发现优先级高的容器,CPU使用率也高
dd if=/dev/zero of=/dev/null &

修改优先级

bash 复制代码
[root@docker-ce2 ~]# cd /sys/fs/cgroup/cpu/docker/da02eea728e508231d997661e347a3daf953d906c8ef19502e80f7e38c1c0b2a/
[root@docker-ce2 da02eea728e508231d997661e347a3daf953d906c8ef19502e80f7e38c1c0b2a]# cat cpu.shares 
1024
[root@docker-ce2 da02eea728e508231d997661e347a3daf953d906c8ef19502e80f7e38c1c0b2a]# echo 10 > cpu.shares 
[root@docker-ce2 da02eea728e508231d997661e347a3daf953d906c8ef19502e80f7e38c1c0b2a]# cat cpu.shares 
10

核心区别总结

对比项 精确控制使用率(cpu-period+cpu-quota 调整争抢优先级(cpu-shares
限制类型 硬限制(无论 CPU 闲忙,上限固定) 软限制(仅争抢时按比例分配)
触发条件 任何时候都生效 仅多容器争抢 CPU 时生效
资源利用灵活性 低(空闲 CPU 也不能多用) 高(空闲时不浪费资源)
适用场景 需严格限制 CPU 上限的场景 需区分容器优先级的场景

8.1.2 限制内存使用

内存若被容器耗尽,宿主机可能因 "内存溢出(OOM)" 崩溃。Docker 可做以下限制:

  • 内存上限 :用 --memory 200m 限制容器最多使用 200MB 内存。

  • 内存 + Swap 总和限制:用--memory-swap 200m将 "内存 + 交换空间(Swap)" 的总用量也限制为 200MB(若只设 --memory,Swap 默认是内存的 2 倍,易导致宿主机 Swap 被占满)。若容器内存超限制,内部进程会被直接杀死,避免影响宿主机。

    bash 复制代码
    [root@docker-ce2 ~]# docker run -d --name test --memory 200M --memory-swap 200M nginx:1.24 
    4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74
    [root@docker-ce2 ~]# cd /sys/fs/cgroup/memory/docker/4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74/
    [root@docker-ce2 4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74]# ls
    cgroup.clone_children           memory.kmem.tcp.max_usage_in_bytes  memory.oom_control
    cgroup.event_control            memory.kmem.tcp.usage_in_bytes      memory.pressure_level
    cgroup.procs                    memory.kmem.usage_in_bytes          memory.soft_limit_in_bytes
    memory.failcnt                  memory.limit_in_bytes               memory.stat
    memory.force_empty              memory.max_usage_in_bytes           memory.swappiness
    memory.kmem.failcnt             memory.memsw.failcnt                memory.usage_in_bytes
    memory.kmem.limit_in_bytes      memory.memsw.limit_in_bytes         memory.use_hierarchy
    memory.kmem.max_usage_in_bytes  memory.memsw.max_usage_in_bytes     notify_on_release
    memory.kmem.slabinfo            memory.memsw.usage_in_bytes         tasks
    memory.kmem.tcp.failcnt         memory.move_charge_at_immigrate
    memory.kmem.tcp.limit_in_bytes  memory.numa_stat
    [root@docker-ce2 4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74]# cat memory.memsw.limit_in_bytes
    209715200

测试:

bash 复制代码
[root@docker-ce2 ~]# ls
公共  文档  anaconda-ks.cfg                         phpmyadmin-latest.tar.gz
模板  下载  index                                   rch.tar.gz
视频  音乐  libcgroup-0.41-19.el8.x86_64.rpm        test
图片  桌面  libcgroup-tools-0.41-19.el8.x86_64.rpm  ubuntu-latest.tar.gz
[root@docker-ce2 ~]# yum install libcgroup-0.41-19.el8.x86_64.rpm -y &> /dev/null
[root@docker-ce2 ~]# yum install libcgroup-tools-0.41-19.el8.x86_64.rpm -y &> /dev/null
[root@docker-ce2 ~]# cd /sys/fs/cgroup/memory/
[root@docker-ce2 memory]# cgexec  -g memory:docker/4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74/ dd if=/dev/zero of=/dev/shm/testfile  bs=1M count=100
记录了100+0 的读入
记录了100+0 的写出
104857600字节(105 MB,100 MiB)已复制,0.0265655 s,3.9 GB/s
[root@docker-ce2 memory]# cgexec  -g memory:docker/4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74/ dd if=/dev/zero of=/dev/shm/testfile  bs=1M count=190
记录了190+0 的读入
记录了190+0 的写出
199229440字节(199 MB,190 MiB)已复制,0.0552161 s,3.6 GB/s
[root@docker-ce2 memory]# cgexec  -g memory:docker/4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74/ dd if=/dev/zero of=/dev/shm/testfile  bs=1M count=195
记录了195+0 的读入
记录了195+0 的写出
204472320字节(204 MB,195 MiB)已复制,0.0515879 s,4.0 GB/s
#超过200M,内存溢出,就会被杀死
[root@docker-ce2 memory]# cgexec  -g memory:docker/4d4e9da18d37892f539af3e462ccb97dca91e01efb34125dfa4a7884e3b6de74/ dd if=/dev/zero of=/dev/shm/testfile  bs=1M count=200
已杀死

8.1.3 限制磁盘 IO

磁盘读写过慢会拖慢宿主机整体性能。Docker 可限制容器对磁盘的读写速度,比如用 --device-write-bps /dev/sda:30MB,限制容器向 /dev/sda 磁盘的写入速率为每秒 30MB,保证磁盘资源不被单个容器独占。

bash 复制代码
[root@docker-ce2 memory]# fdisk -l #查看磁盘名称
Disk /dev/sda:100 GiB,107374182400 字节,209715200 个扇区

[root@docker-ce2 memory]# docker run -it --rm --name test --device-write-bps /dev/sda:30M ubuntu:latest 
root@23244c6207c8:/# dd if=/dev/zero of=/mnt/testfile bs=1M count=100 oflag=direct #设定dd命令直接写入磁盘
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 3.34259 s, 31.4 MB/s

8.2 安全加固:让容器隔离更 "可靠"

Docker 默认用 Linux 命名空间(Namespace) 隔离容器(如进程、网络、文件系统各自独立),但这种隔离并非 "绝对安全",需进一步加固。

8.2.1 Docker 默认隔离性

Docker 默认隔离是 "轻量级" 的,存在不足:

  • 所有容器共享宿主机内核,若内核有漏洞,容器内的恶意程序可能突破隔离。
  • 容器内的 root 用户,在宿主机中对应 "有一定权限" 的用户(并非完全无权限的 "假 root"),若容器被入侵,攻击者可能借助该权限破坏宿主机。
  • 容器内仍能看到宿主机的部分系统信息(如 /sys 目录内容)。
bash 复制代码
#容器内部
[root@docker-ce2 ~]# docker  run -it --name test --rm --memory 200M --memory-swap=200M ubuntu:latest 
root@029ee3d05dab:/# free -m
               total        used        free      shared  buff/cache   available
Mem:            3872        1970         977         215        1381        1902
Swap:           4095           1        4094

#宿主机
[root@docker-ce2 ~]# free -m
               total        used        free      shared  buff/cache   available
Mem:            3872        1970         977         215        1381        1902
Swap:           4095           1        4094
#发现内存没有隔离

8.2.2 解决默认隔离性的不足----不同的容器读取不同的文件

  • LXCFS 是一个为 LXC(Linux Containers)容器提供增强文件系统功能的工具。

    主要功能

    1. 资源可见性

      • LXCFS 可以使容器内的进程看到准确的 CPU、内存和磁盘 I/O 等资源使用信息。在没有 LXCFS 时,容器内看到的资源信息可能不准确,这会影响到在容器内运行的应用程序对资源的评估和管理。
    2. 性能监控

      • 方便对容器内的资源使用情况进行监控和性能分析。通过提供准确的资源信息,管理员和开发人员可以更好地了解容器化应用的性能瓶颈,并进行相应的优化。

安装lxcfs

bash 复制代码
[root@docker-ce2 ~]# ls
公共  文档  anaconda-ks.cfg                         lxcfs-5.0.4-1.el9.x86_64.rpm           rch.tar.gz
模板  下载  index                                   lxc-libs-4.0.12-1.el9.x86_64.rpm       test
视频  音乐  libcgroup-0.41-19.el8.x86_64.rpm        lxc-templates-4.0.12-1.el9.x86_64.rpm  ubuntu-latest.tar.gz
图片  桌面  libcgroup-tools-0.41-19.el8.x86_64.rpm  phpmyadmin-latest.tar.gz
[root@docker-ce2 ~]# yum install lxcfs-5.0.4-1.el9.x86_64.rpm lxc-libs-4.0.12-1.el9.x86_64.rpm lxc-templates-4.0.12-1.el9.x86_64.rpm -y 

运行lxcfs

bash 复制代码
[root@docker-ce2 ~]# ll /var/lib/lxcfs/
总用量 0
[root@docker-ce2 ~]# lxcfs /var/lib/lxcfs/ &
[1] 6187
[root@docker-ce2 ~]# Running constructor lxcfs_init to reload liblxcfs
mount namespace: 4
hierarchies:
  0: fd:   5: name=systemd
  1: fd:   6: devices
  2: fd:   7: rdma
  3: fd:   8: freezer
  4: fd:   9: pids
  5: fd:  10: net_cls,net_prio
  6: fd:  11: misc
  7: fd:  12: cpuset
  8: fd:  13: cpu,cpuacct
  9: fd:  14: blkio
 10: fd:  15: perf_event
 11: fd:  16: memory
 12: fd:  17: hugetlb
Kernel supports pidfds
Kernel supports swap accounting
api_extensions:
- cgroups
- sys_cpu_online
- proc_cpuinfo
- proc_diskstats
- proc_loadavg
- proc_meminfo
- proc_stat
- proc_swaps
- proc_uptime
- proc_slabinfo
- shared_pidns
- cpuview_daemon
- loadavg_daemon
- pidfds
[root@docker-ce2 ~]# ps
    PID TTY          TIME CMD
   4030 pts/5    00:00:00 bash
   6187 pts/5    00:00:00 lxcfs
   6191 pts/5    00:00:00 ps

让容器读的文件不再是/proc下面的文件,而是/var/lib/lxcfs/proc/下面的文件

bash 复制代码
[root@docker-ce2 ~]# cd /var/lib/lxcfs
[root@docker-ce2 lxcfs]# ll
总用量 0
drwxr-xr-x 2 root root 0 10月 16 14:08 cgroup
dr-xr-xr-x 2 root root 0 10月 16 14:08 proc
dr-xr-xr-x 2 root root 0 10月 16 14:08 sys
[root@docker-ce2 lxcfs]# cd
[root@docker-ce2 ~]# docker run  -it -m 256m \
-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \
-v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \
-v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \
-v /var/lib/lxcfs/proc/stat:/proc/stat:rw \
-v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \
-v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \
ubuntu
root@e93ccfd9f952:/# free -m
               total        used        free      shared  buff/cache   available
Mem:             256           1         254           0           0         254
Swap:            512           0         512

8.2.3 容器特权(谨慎使用)----打开所有特权

某些特殊场景(如容器需操作宿主机网卡、硬件),需给容器高权限,此时可通过 --privileged 启动特权容器 。但风险极大 ------ 容器内的 root 几乎与宿主机 root 权限一致,能直接修改宿主机内核、设备,因此非必要绝不使用

bash 复制代码
[root@docker-ce2 ~]# docker run -it --name test --rm ubuntu:latest 
root@1bf72d037011:/# exit
exit
[root@docker-ce2 ~]# docker run -it --name test --rm busybox:latest 
/ # ifconfig 
eth0      Link encap:Ethernet  HWaddr B6:4B:64:4B:D2:EF  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1828 (1.7 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

/ # ifconfig  eth0 172.17.0.3 netmask 255.255.255.0
ifconfig: SIOCSIFADDR: Operation not permitted
/ # exit
[root@docker-ce2 ~]# docker run -it --name test --rm --privileged busybox:latest 
/ # ifconfig  eth0 172.17.0.3 netmask 255.255.255.0
/ # ifconfig 
eth0      Link encap:Ethernet  HWaddr 22:19:CD:24:D9:AF  
          inet addr:172.17.0.3  Bcast:172.17.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:17 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:2306 (2.2 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

8.2.4 容器特权的白名单----打开分类的特权

若必须给容器部分权限,不要用 "特权模式" 全放开,而是通过 --cap-add 指定具体需要的权限(如仅添加 "配置网络" 的能力)。这样既满足需求,又避免过度授权,更安全。

capabilities手册地址:http://man7.org/linux/man-pages/man7/capabilities.7.html

bash 复制代码
[root@docker-ce2 ~]# docker run -it --name test --rm --cap-add NET_ADMIN  busybox:latest 
/ # ifconfig 
eth0      Link encap:Ethernet  HWaddr 7E:82:67:2E:CC:4D  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:17 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:2306 (2.2 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

/ # ifconfig eth0 172.17.0.3 netmask 255.255.255.0
/ # ifconfig 
eth0      Link encap:Ethernet  HWaddr 7E:82:67:2E:CC:4D  
          inet addr:172.17.0.3  Bcast:172.17.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:18 errors:0 dropped:0 overruns:0 frame:0
          TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:2376 (2.3 KiB)  TX bytes:126 (126.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

/ # fdisk -l

9.容器编排工具 Docker Compose

Docker 能方便运行单个容器 ,但实际项目里,一个应用往往需要多个容器配合 (比如 Web 服务 + 数据库 + 缓存)。手动逐个启动、配置这些容器既麻烦又容易出错,这时候就需要 Docker Compose ------ 它能通过一个配置文件,一键管理多容器的 "启动、停止、联动"。

9.1 Docker Compose 概述

Docker Compose 是 Docker 官方的容器编排工具,专门简化 "多容器应用" 的部署与管理。

举个例子:假设要搭建一个博客系统,需要两个容器:

  • Nginx 容器(提供 Web 服务);
  • MySQL 容器(存储博客数据)。

不用 Compose 时,你得:

  1. 先启动 MySQL 容器,手动配置端口、密码、数据卷;
  2. 再启动 Nginx 容器,手动配置端口,并将其与 MySQL 容器关联。

用 Compose 时,只需写一个 docker-compose.yml 配置文件,把两个容器的配置都写进去,再执行 docker compose up,就能自动按顺序启动所有容器,并处理它们的依赖关系

9.2 Docker Compose 的常用命令参数

yml 复制代码
# 创建测试目录并进入
[root@docker-ce2 test]# mkdir test
[root@docker-ce2 test]# cd test
# 编辑配置文件
[root@docker-ce2 test]# vim docker-compose.yml
services:
  web:  # 服务名称:web(可自定义)
    image: nginx  # 使用nginx镜像
    ports:
      - "80:80"  # 主机80端口映射到容器80端口

  db:  # 服务名称:db(可自定义)
    image: mysql:8.0  # 使用mysql 8.0镜像
    environment:
      MYSQL_ROOT_PASSWORD: 123  # 配置MySQL root密码

以下是一些 Docker Compose 常用命令:

一、服务管理

  1. docker-compose up

    • 启动配置文件中定义的所有服务。

    • 可以使用 -d 参数在后台启动服务。

    • 可以使用-f 来指定yml文件

    • 例如:docker-compose up -d

      bash 复制代码
      # 后台启动当前目录下的docker-compose.yml中定义的服务
      [root@docker test]# docker compose up -d
      [+] Running 2/2
       ✔ Container test-db-1   Started            0.9s
       ✔ Container test-web-1  Started            0.9s
       
      [root@docker ~]# docker compose -f  test/docker-compose.yml  up -d
      [+] Running 3/3
       ✔ Network test_default  Created             0.1s
       ✔ Container test-web-1  Started             0.9s
       ✔ Container test-db-1   Started   

      up 是最常用的启动命令,加 -d 就不用一直盯着终端了,想指定别的配置文件就用 -f

  2. docker-compose down

    • 停止并删除配置文件中定义的所有服务以及相关的网络和存储卷。

      bash 复制代码
      [root@docker test]# docker compose down
      [+] Running 3/3
       ✔ Container test-db-1   Removed                                                          1.7s
       ✔ Container test-web-1  Removed                                                          0.3s
       ✔ Network test_default  Removed                                                          0.1s

      down 相当于 "彻底清理",不用手动删容器和网络,适合重新部署时用。

  3. docker-compose start

    • 启动已经存在的服务,但不会创建新的服务。

      bash 复制代码
      [root@docker test]# docker compose start
      [+] Running 2/2
       ✔ Container test-db-1   Started                                                                 
       ✔ Container test-web-1  Started   

      如果只是暂停了服务(用 stop),想再启动就用 start,比 up 快,因为不用重新创建容器。

  4. docker-compose stop

    • 停止正在运行的服务

      bash 复制代码
      [root@docker test]# docker compose stop
      [+] Stopping 2/2
       ✔ Container test-web-1  Stopped                                                          0.4s
       ✔ Container test-db-1   Stopped                                                         10.3s

      想暂时停服务但不删容器,就用 stop,后续可以用 start 快速恢复。

  5. docker-compose restart

    • 重启服务。

      bash 复制代码
      [root@docker test]# docker compose restart
      [+] Restarting 2/2
       ✔ Container test-web-1  Started                                                                     
       ✔ Container test-db-1   Started  

      改了配置想让服务生效?用 restart 一键重启,比手动停了再启动方便。

二、服务状态查看

  1. docker-compose ps

    • 列出正在运行的服务以及它们的状态,包括容器 ID、名称、端口映射等信息。

      复制代码
      [root@docker test]# docker compose ps
      NAME         IMAGE       COMMAND                   SERVICE   CREATED         STATUS          PORTS
      test-db-1    mysql:5.7   "docker-entrypoint.s..."   db        2 minutes ago   Up 48 seconds   3306/tcp, 33060/tcp
      test-web-1   nginx       "/docker-entrypoint...."   web       2 minutes ago   Up 50 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp

      想知道服务有没有启动成功?端口映射对不对?用 ps 一目了然。

  2. docker-compose logs

    • 查看服务的日志输出。可以指定服务名称来查看特定服务的日志。

      复制代码
      [root@docker test]# docker compose  logs db

      服务启动失败?用 logs 看日志找原因,指定服务名能减少干扰。

三、构建和重新构建服务(了解)

  1. docker-compose build

    • 构建配置文件中定义的服务的镜像。可以指定服务名称来只构建特定的服务。

      yml 复制代码
      # 先准备两个Dockerfile
      [root@docker test]# cat Dockerfile  # 第一个Dockerfile:创建一个文件
      FROM  busybox:latest
      RUN   touch /leefile1
      
      [root@docker test]# cat test.yml  # 配置文件:定义两个服务,基于上面的Dockerfile构建
      services:
        test1:
          image: test1                    # 生成的镜像名称
          build:
            context: /root/test            # Dockerfile所在目录
            dockerfile: Dockerfile        # 指定用哪个Dockerfile(默认找Dockerfile)
          command: ["/bin/sh","-c","sleep 3000"]  # 启动后休眠3000秒(保持运行)
          restart: always  # 容器退出后自动重启
          container_name: busybox1  # 自定义容器名称
      
        test2:
          image: test2
          build:
            context: /root/test                
            dockerfile: Dockerfile
          command: ["/bin/sh","-c","sleep 3000"]
          restart: always
          container_name: busybox2
      
      # 构建所有服务的镜像
      [root@docker test]# docker compose -f test.yml build            
      # 只构建test1服务的镜像
      [root@docker test]# docker compose  -f test.yml build test1        

      如果服务用的不是现成镜像,而是自己写 Dockerfile 构建的,就用 build 命令生成镜像。

  2. docker-compose up --build

    • 启动服务并在启动前重新构建镜像。
    bash 复制代码
    [root@docker test]# docker compose  -f test.yml  up -d			#会去仓库拉去镜像
    [+] Running 1/1
     ! test1 Warning pull access denied for test1, repository does not exist or may require 'docker login': denied: requested acces...  
     
     	
     [root@docker test]# docker compose  -f test.yml  up --build	#会先构建镜像后启动容器

    改了 Dockerfile 后,想让新改动生效?用 up --build 一键搞定构建和启动。

四、其他操作

  1. docker-compose exec

    • 在正在运行的服务容器中执行命令。

      复制代码
      services:
        test:
          image: busybox
          command: ["/bin/sh","-c","sleep 3000"]
          restart: always
          container_name: busybox1
      
      [root@docker test]# docker compose -f test.yml  up -d
      [root@docker test]# docker compose  -f test.yml  exec  test sh
      / #

      容器启动后,想进去看看文件或执行命令?用 exec 加服务名,不用记复杂的容器 ID。

  2. docker-compose pull

    • 拉取配置文件中定义的服务所使用的镜像。

      [root@docker test]# docker compose -f test.yml pull
      [+] Pulling 2/2
      ✔ test Pulled
      ✔ ec562eabd705 Pull complete

    服务器网速慢?先 pull 把镜像下好,后面启动就快了。

  3. docker-compose config

    • 验证并查看解析后的 Compose 文件内容

      复制代码
      [root@docker test]# docker compose -f test.yml  config
      name: test
      services:
        test:
          command:
            - /bin/sh
            - -c
            - sleep 3000
          container_name: busybox1
          image: busybox
          networks:
            default: null
          restart: always
      networks:
        default:
          name: test_default
      [root@docker test]# docker compose -f test.yml  config -q

9.3 Docker Compose 的 yaml 文件

Compose 的核心是 docker-compose.yml(或 .yaml)配置文件,用YAML 格式定义 "容器(服务)、网络、数据卷" 等资源。

一、服务(services)

  1. 服务名称(service1_name/service2_name 等)

    • 每个服务在配置文件中都有一个唯一的名称,用于在命令行和其他部分引用该服务。

      yml 复制代码
      services:
        web:  # 服务1:web(可自定义)
          # 服务1的配置
        mysql:  # 服务2:mysql(可自定义)
          # 服务2的配置

      给每个容器起个好记的名字,方便后续操作。

  2. 镜像(image)

    • 指定服务所使用的 Docker 镜像名称和标签。例如,image: nginx:latest 表示使用 nginx 镜像的最新版本

      yml 复制代码
      services:
        web:
          image: nginx  # 使用nginx最新版镜像
        mysql:
          image: mysql:5.7  # 使用mysql 5.7版本镜像

      告诉 Compose,这个服务用哪个现成的镜像来创建容器。

  3. 端口映射(ports)

    • 将容器内部的端口映射到主机的端口,以便外部可以访问容器内的服务。例如,- "8080:80" 表示将主机的 8080 端口映射到容器内部的 80 端口。

      yml 复制代码
      services:
        web:
          image: timinglee/mario
          container_name: game			#指定容器名称
          restart: always				   #docekr容器自动启动
          expose:
          	- 1234						#指定容器暴露那些端口,些端口仅对链接的服务可见,不会映射到主机的端口
          ports:
            - "80:8080"

      容器内部的服务(比如 8080 端口的 Web 服务),想让外部通过主机的 80 端口访问?就用 ports 做映射。

  4. 环境变量(environment)

    • 为容器设置环境变量,可以在容器内部的应用程序中使用。例如,VAR1: value1 设置环境变量 VAR1 的值为 value1

      yml 复制代码
      services:
        mysql:
          image: mysql:5.7
          environment:
            MYSQL_ROOT_PASSWORD: lee  # MySQL的root用户密码设为lee

      很多软件(比如 MySQL)需要通过环境变量配置,这里直接写进去,容器启动时会自动生效。

  5. 存储卷(volumes)

    • 将主机上的目录或文件挂载到容器中,以实现数据持久化或共享。例如,- /host/data:/container/data 将主机上的 /host/data 目录挂载到容器内的 /container/data 路径。

      yml 复制代码
      services:
        test:
          image: busybox
          command: ["/bin/sh","-c","sleep 3000"]
          restart: always
          container_name: busybox1
          volumes:
            - /etc/passwd:/tmp/passwd:ro			#只读挂在本地文件到指定位置

      容器里的数据默认是临时的,想保存(比如数据库数据)或让容器用主机的文件(比如配置),就用 volumes 挂载。

  6. 网络(networks)

    • 将服务连接到特定的网络,以便不同服务的容器可以相互通信

      复制代码
      services:
        web:
          image: nginx
          container_name: webserver
          network_mode: bridge				#使用本机自带bridge网络
      
      services:
        test:
          image: busybox 
          container_name: webserver
          command: ["/bin/sh","-c","sleep10000000"]
          #network_mode: mynet2
          networks:
            - mynet1
            - mynet2
      
      
      networks:
        mynet1:
          driver: bridge
      
        mynet2:
          driver: bridge
      • 大白话思路:容器之间想通信?得在同一个网络里。默认会创建一个网络,也可以自己定义多个,控制哪些容器能互相访问。
  7. 命令(command)

    • 覆盖容器启动时默认执行的命令。例如,command: python app.py 指定容器启动时运行 python app.py 命令

      复制代码
      [root@docker test]# vim busybox.yml
      services:
        web:
          image: busybox
          container_name: busybox
          #network_mode: mynet2
          command: ["/bin/sh","-c","sleep10000000"]

      想让容器启动后执行特定命令(比如不启动默认服务,而是休眠保持运行),就用 command 覆盖。

二、网络(networks)

  • 定义 Docker Compose 应用程序中使用的网络。可以自定义网络名称和驱动程序等属性。

  • 默认情况下docker compose 在执行时会自动建立网路

    yml 复制代码
    services:
      busybox1:
        image: busybox
        container_name: busybox1
        command:  # 保持运行
          - /bin/sh
          - -c
          - sleep 10000
        networks:  # 加入mynet1和mynet2网络
          - mynet1
          - mynet2
    
    
      busybox2:
        image: busybox
        container_name: busybox2
        networks:  # 只加入mynet1网络(所以只能和busybox1通过mynet1通信)
          - mynet1
        command:
          - /bin/sh
          - -c
          - sleep 10000
    
    # 网络配置(和services同级)
    networks:
      mynet1:
        driver: bridge  # 桥接模式(同一主机内容器通信)
    
      default:
        external: true  # 不创建新网络,使用外部已存在的网络
        name: bridge    # 外部网络名称(主机默认的bridge网络)
    
      mynet2:
        ipam:  # 自定义IP段
          driver: default
          config:
            - subnet: 172.25.0.0/16  # 子网
              gateway: 172.25.0.254  # 网关

    网络就像 "局域网",networks 配置可以创建多个 "局域网",控制哪些容器在同一个 "局域网" 里(能互相访问),还能指定 IP 段。

三、存储卷(volumes)

  • 定义 Docker Compose 应用程序中使用的存储卷。可以自定义卷名称和存储位置等属性。

    yml 复制代码
    services:
      busybox1:
        image: busybox
        container_name: busybox1
        command:
          - /bin/sh
          - -c
          - sleep 10000
        networks:
          - mynet1
          - mynet2
        volumes:
          - data:/data  # 挂载名为data的卷到容器的/data目录
    
      busybox2:
        image: busybox
        container_name: busybox2
        networks:
          - mynet1
        command:
          - /bin/sh
          - -c
          - sleep 10000
    
    networks:
      mynet1:
        driver: bridge
      default:
        external: true
        name: bridge
      mynet2:
        ipam:
          driver: default
          config:
            - subnet: 172.25.0.0/16
              gateway: 172.25.0.254
    
    # 卷配置(和services同级)
    volumes:
      data:  # 卷名称
        name: rch  # 自定义卷的实际名称(默认会加项目前缀)

    volumes 定义的是 "独立存储卷",多个服务可以挂载同一个卷共享数据,而且卷的数据不会因为容器删除而丢失,比直接挂载主机目录更安全。

9.4 企业示例:Haproxy + Nginx 负载均衡

用 Docker Compose 部署 Haproxy(负载均衡)+ 两个 Nginx(Web 服务),实现访问负载均衡器时,自动分发到两个 Nginx 上。

bash 复制代码
[root@docker-ce2 ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
nginx        1.24      6c0218f16876   2 years ago     142MB  # Nginx镜像(提供Web服务)
haproxy      2.3       7ecd3fda00f4   3 years ago     99.4MB  # Haproxy镜像(负载均衡)

Haproxy + Nginx 负载均衡架构的结构图:

  • 用户访问主机的 80 端口 → Haproxy 容器(监听 80 端口)→ 转发到 web1 或 web2(Nginx 容器)

  • web1 和 web2 分别提供不同的页面(用于验证负载均衡效果)

步骤 1:编写 docker-compose.yml

bash 复制代码
[root@docker-ce2 test]# cat docker-compose.yml 
services:
  web1:  # 第一个Nginx服务
    image: nginx:1.24  # 使用nginx 1.24镜像
    container_name: webserver1  # 容器名:webserver1
    networks: 
      - mynet2  # 加入mynet2网络(和Haproxy通信)
    expose:
      - 80  # 暴露80端口给同一网络的容器(Haproxy)
    volumes:
      - /docker/web/html1:/usr/share/nginx/html  # 挂载主机目录到Nginx的网页目录(自定义页面)

  web2:  # 第二个Nginx服务(和web1类似)
    image: nginx:1.24
    container_name: webserver2
    networks: 
      - mynet2
    expose:
      - 80
    volumes:
      - /docker/web/html2:/usr/share/nginx/html  # 另一个目录,内容不同

  haproxy:  # Haproxy负载均衡服务
    image: haproxy:2.3
    networks:
      - mynet1  # 可选:用于其他服务访问
      - mynet2  # 和web1、web2在同一网络,才能转发请求
    expose:
      - 80  # 容器内部80端口
    ports:
      - "80:80"  # 主机80端口映射到Haproxy的80端口(用户访问入口)
    volumes:
      - /docker/conf/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg  # 挂载Haproxy的配置文件

# 定义网络
networks:
  mynet1:
    driver: bridge
  mynet2:
    driver: bridge
  • 搞两个 Nginx(web1 和 web2),分别挂载不同目录放不同页面,加入 mynet2 网络
  • Haproxy 也加入 mynet2,这样能访问到 web1 和 web2;同时把主机 80 端口映射到 Haproxy 的 80,作为用户入口
  • Haproxy 的配置文件通过卷挂载进去,控制请求如何分发

步骤 2:准备目录和配置文件

bash 复制代码
# 创建需要的目录(按docker-compose.yml中的挂载路径)
[root@docker-ce2 test]# mkdir -p /docker/conf/haproxy /docker/web/html1 /docker/web/html2

# 复制Haproxy配置文件(先装haproxy拿默认配置,用完删掉)
[root@docker-ce2 test]# yum install haproxy.x86_64  -y &> /dev/null  # 安装haproxy获取默认配置
[root@docker-ce2 test]# cp /etc/haproxy/haproxy.cfg /docker/conf/haproxy/haproxy.cfg  # 复制到挂载目录
[root@docker-ce2 test]# yum remove haproxy.x86_64  -y &> /dev/null  # 不需要了,删掉

# 目录结构如下
[root@docker-ce2 test]# tree /docker/
/docker/
├── conf
│   └── haproxy
│       └── haproxy.cfg  # Haproxy配置文件
└── web
    ├── html1  # web1的网页目录
    └── html2  # web2的网页目录

步骤 3:配置 Haproxy 和 Web 页面

编辑 Haproxy 配置文件(核心是定义转发规则),准备 Web 页面内容

bash 复制代码
[root@docker-ce2 test]# grep -Ev "^*#|^$" /docker/conf/haproxy/haproxy.cfg 
global
    log         127.0.0.1 local2
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon
    stats socket /var/lib/haproxy/stats
    ssl-default-bind-ciphers PROFILE=SYSTEM
    ssl-default-server-ciphers PROFILE=SYSTEM
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000
# 核心:定义负载均衡规则(监听80端口,转发到web1和web2)
listen rch-webserver
    mode http  # HTTP模式
    bind :80  # 监听容器的80端口(已映射到主机80)
    # 转发规则:请求分发到webserver1和webserver2(容器名),每5秒检查一次健康状态,3次失败标记为不可用
    server web1 webserver1:80 check inter 5 fall 3
    server web2 webserver2:80 check inter 5 fall 3
####这里web就可以不用写IP,可以直接写容器名,因为自定义的bridge自带域名解析
[root@docker-ce2 ~]# echo web1 > /docker/web/html1/index.html  # web1的页面显示web1
[root@docker-ce2 ~]# echo web2 > /docker/web/html2/index.html  # web2的页面显示web2

步骤 4:启动服务并测试

bash 复制代码
# 启动所有服务
[root@docker-ce2 test]# docker compose up -d
[+] Running 5/5
 ✔ Network test_mynet1       Created                                                                       0.1s 
 ✔ Network test_mynet2       Created                                                                       0.1s 
 ✔ Container webserver2      Started                                                                       0.4s 
 ✔ Container test-haproxy-1  Started                                                                       0.6s 
 ✔ Container webserver1      Started                                                                       0.5s 

# 查看运行的容器
[root@docker-ce2 test]# docker ps
CONTAINER ID   IMAGE         COMMAND                   CREATED         STATUS         PORTS                                 NAMES
fe17559683c8   nginx:1.24    "/docker-entrypoint...."   5 seconds ago   Up 4 seconds   80/tcp                                webserver2
832f7750378b   nginx:1.24    "/docker-entrypoint...."   5 seconds ago   Up 4 seconds   80/tcp                                webserver1
607c2d21ce45   haproxy:2.3   "docker-entrypoint.s..."   5 seconds ago   Up 4 seconds   0.0.0.0:80->80/tcp, [::]:80->80/tcp   test-haproxy-1

测试负载均衡效果(在另一台主机上访问):

bash 复制代码
[root@docker-ce ~]# curl 192.168.2.51  # 访问主机IP(Haproxy映射的80端口)
web1  # 第一次到web1
[root@docker-ce ~]# curl 192.168.2.51
web2  # 第二次到web2
[root@docker-ce ~]# curl 192.168.2.51
web1  # 第三次到web1(轮询模式,默认规则)
[root@docker-ce ~]# curl 192.168.2.51
web2

注意 :启动时如果 Haproxy 无法启动,可能是配置文件路径错了。Haproxy 镜像的默认配置文件路径是 /usr/local/etc/haproxy/haproxy.cfg,不是系统默认的 /etc/haproxy/haproxy.cfg,需要按镜像要求挂载。

127.0.0.1 local2

chroot /var/lib/haproxy

pidfile /var/run/haproxy.pid

maxconn 4000

user haproxy

group haproxy

daemon

stats socket /var/lib/haproxy/stats

ssl-default-bind-ciphers PROFILE=SYSTEM

ssl-default-server-ciphers PROFILE=SYSTEM

defaults

mode http

log global

option httplog

option dontlognull

option http-server-close

option forwardfor except 127.0.0.0/8

option redispatch

retries 3

timeout http-request 10s

timeout queue 1m

timeout connect 10s

timeout client 1m

timeout server 1m

timeout http-keep-alive 10s

timeout check 10s

maxconn 3000

核心:定义负载均衡规则(监听80端口,转发到web1和web2)

listen rch-webserver

mode http # HTTP模式

bind :80 # 监听容器的80端口(已映射到主机80)

转发规则:请求分发到webserver1和webserver2(容器名),每5秒检查一次健康状态,3次失败标记为不可用

server web1 webserver1:80 check inter 5 fall 3

server web2 webserver2:80 check inter 5 fall 3

####这里web就可以不用写IP,可以直接写容器名,因为自定义的bridge自带域名解析

root@docker-ce2 \~\]# echo web1 \> /docker/web/html1/index.html # web1的页面显示web1 \[root@docker-ce2 \~\]# echo web2 \> /docker/web/html2/index.html # web2的页面显示web2 ### 步骤 4:启动服务并测试 ```bash # 启动所有服务 [root@docker-ce2 test]# docker compose up -d [+] Running 5/5 ✔ Network test_mynet1 Created 0.1s ✔ Network test_mynet2 Created 0.1s ✔ Container webserver2 Started 0.4s ✔ Container test-haproxy-1 Started 0.6s ✔ Container webserver1 Started 0.5s # 查看运行的容器 [root@docker-ce2 test]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES fe17559683c8 nginx:1.24 "/docker-entrypoint...." 5 seconds ago Up 4 seconds 80/tcp webserver2 832f7750378b nginx:1.24 "/docker-entrypoint...." 5 seconds ago Up 4 seconds 80/tcp webserver1 607c2d21ce45 haproxy:2.3 "docker-entrypoint.s..." 5 seconds ago Up 4 seconds 0.0.0.0:80->80/tcp, [::]:80->80/tcp test-haproxy-1 测试负载均衡效果(在另一台主机上访问): ```bash [root@docker-ce ~]# curl 192.168.2.51 # 访问主机IP(Haproxy映射的80端口) web1 # 第一次到web1 [root@docker-ce ~]# curl 192.168.2.51 web2 # 第二次到web2 [root@docker-ce ~]# curl 192.168.2.51 web1 # 第三次到web1(轮询模式,默认规则) [root@docker-ce ~]# curl 192.168.2.51 web2 ``` **注意** :启动时如果 Haproxy 无法启动,可能是配置文件路径错了。Haproxy 镜像的默认配置文件路径是 `/usr/local/etc/haproxy/haproxy.cfg`,不是系统默认的 `/etc/haproxy/haproxy.cfg`,需要按镜像要求挂载。

相关推荐
郝学胜-神的一滴3 小时前
Linux系统函数link、unlink与dentry的关系及使用注意事项
linux·运维·服务器·开发语言·前端·c++
霍格沃兹软件测试开发3 小时前
借助 Dify 实现自动化工作流,每天节省3小时
运维·ai·自动化
无妄无望3 小时前
docker学习 (3)网络与防火墙
网络·学习·docker
星空的资源小屋3 小时前
RoboIntern,一款自动化办公小助手
运维·人工智能·pdf·自动化·电脑·excel
Pota-to成长日记3 小时前
2025/10/14 redis断联 没有IPv4地址 (自用)
linux·运维·服务器
樱木...3 小时前
Linux 查询目录下文件大小引发的内存溢出问题
linux·运维
.小墨迹3 小时前
linux删除通过源码安装的库
linux·运维·chrome
~黄夫人~3 小时前
Ubuntu系统快速上手命令(详细)
linux·运维·笔记·ubuntu·postgresql
发光的沙子3 小时前
FPGA----petalinux的Ubuntu文件系统移植
linux·运维·ubuntu