一、Docker简介及部署方法
1.1Docker简介
1.1.1什么是docker
Docker是管理容器的引擎,为应用打包、部署平台,而非单纯的虚拟化技术
docker的重要特点和优势:
1. 轻量级虚拟化 | Docker 容器相较于传统的虚拟机更加轻量和高效,能够快速启动和停止,节省系统资源。 例如,启动一个 Docker 容器可能只需要几秒钟,而启动一个虚拟机则可能需要几分钟。 |
2. 一致性 | 确保应用程序在不同的环境中(如开发、测试、生产)具有一致的运行表现。 无论在本地还是云端,应用的运行环境都能保持相同,减少了因环境差异导致的问题。 |
3. 可移植性 | 可以轻松地将 Docker 容器从一个平台迁移到另一个平台,无需担心依赖和环境配置的差 异。 比如,在本地开发的容器可以无缝部署到云服务器上。 |
4. 高效的资源利用 | 多个 Docker 容器可以共享主机的操作系统内核,从而更有效地利用系统资 源。 |
5. 易于部署和扩展 | 能够快速部署新的应用实例,并且可以根据需求轻松地进行水平扩展。 |
总之,Docker 极大地简化了应用程序的开发、部署和管理流程,提高了开发效率和运维的便利性。 它在现代软件开发和云计算领域得到了广泛的应用。
1.1.2docker与虚拟化的对比
虚拟机 | docker容器 | |
操作系统 | 宿主机上运行虚拟机OS | 共享宿主机OS |
存储 | 镜像较大(GB) | 镜像小(MB) |
性能 | 操作系统额外的cpu、内存消耗 | 几乎无性能损耗 |
移植性 | 笨重、与虚拟化技术耦合度高 | 轻量、灵活迁移 |
隔离性 | 完全隔离 | 安全隔离 |
部署 | 慢、分钟级 | 快速、秒级 |
运行密度 | 一般几十个 | 单机支持上千容器 |
1.2部署docker
1.2.1容器的工作原理
镜像:容器的母盘
容器:真正运行的业务实例,靠镜像运行起来(在镜像中打开容器)。
仓库:存放镜像
工作原理:
当我们有容器就可以直接运行业务,如果没有就需要去仓库把镜像拉过来运行起来就是容器。
1.2.2部署一个容器
这里因为我们有压缩包所以直接安装的
[root@docker-node2 ~]# ls
anaconda-ks.cfg Desktop Documents Music Public Videos
busybox-latest.tar.gz docker.tar.gz Downloads Pictures Templates
[root@docker-node2 ~]# tar zxf docker.tar.gz
[root@docker-node2 ~]# ls
[root@docker-node2 ~]# dnf install *.rpm -y --allowerasing——如果有冲突加上这个参数
1.2.2.1配置软件仓库
安装正常部署的话需要先配软件源,这里以企业7为例
]# cd /etc/yum.repos.d
]# vim docker.repo
[docker]
name=docker-ce
baseurl=https://mirrors.tuna.tsinghua.edu.cn/docker ce/linux/centos/7/x86_64/stable/ gpgcheck=0
[centos]
name=extras
baseurl=https://mirrors.tuna.tsinghua.edu.cn/centos/7/extras/x86_64
gpgcheck=0
1.2.2.2安装docker-ce并启动服务
#安装docker ]# yum install -y docker-ce
#编辑docker启动文件,设定其使用iptables的网络设定方式,默认使用nftables [root@docker ~]# vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --iptables=true
]# systemctl enable --now docker
]# docker info
1.2.2.3 激活内核网络选项
#在rhel7中 需要
]# vim /etc/sysctl.d/docker.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
]# sysctl --system
]# systemctl restart docker
二、Docker的基本操作
2.1Docker镜像管理
2.1.1搜索镜像
[root@docker-node1 ~]# docker search nginx
2.1.2拉取镜像
[root@docker-node1 ~]# docker pull nginx
2.1.3查看镜像信息
[root@Docker-node1 ~]# docker image inspect nginx:1.26-alpine
2.1.4导出镜像
#保存镜像
[root@Docker-node1 ~]# docker image save nginx:latest -o nginx-latest.tar.gz [root@Docker-node1 ~]# docker image save nginx:latest nginx:1.26-alpine -o nginx.tag.gz
#保存所有镜像
[root@Docker-node1 ~]# docker save `docker images | awk 'NR>1{print $1":"$2}'` o images.tar.gz
2.1.5删除镜像
[root@Docker-node1 ~]# docker rmi nginx:latest
[root@Docker-node1 ~]# docker rmi `docker images | awk 'NR>1{print $1":"$2}'`
2.1.6恢复镜像
[root@Docker-node1 ~]# docker load -i nginx.latest.tar.gz
2.2容器的常用操作
2.2.1启动容器
参数说明
-d | #后台运行 |
-i | #交互式运行 |
-t | #打开一个终端 |
--name | #指定容器名称 |
-p | #端口映射 -p 80:8080 把容器8080端口映射到本机80端口 |
--rm | #容器停止自动删除容器 |
--network | #指定容器使用的网络 |
[root@Docker-node1 ~]# docker run -d --name mario -p 80:8080 timinglee/mario [root@Docker-node1 ~]# docker run -it --name centos7 centos:7
[root@3ba22e59734f /]# #进入到容器中,按+退出并停止容器,#按+退出但 不停止容器
#重新进入容器
[root@docker ~]# docker attach centos7
[root@3ba22e59734f /]#
#在容器中执行命令
[root@docker ~]# docker exec -it test ifconfig
2.2.2查看容器运行信息
[root@Docker-node1 ~]# docker ps #查看当前运行容器
[root@Docker-node1 ~]# docker ps -a #查看所有容器
[root@Docker-node1 ~]# docker inspect busybox #查看容器运行的详细信息
2.2.3停止和运行容器
[root@Docker-node1 ~]# docker stop busybox #停止容器
[root@Docker-node1 ~]# docker kill busybox #杀死容器,可以使用信号
[root@Docker-node1 ~]# docker start busybox #开启停止的容器
2.2.4删除容器
[root@Docker-node1 ~]# docker rm centos7 #删除停止的容器
[root@Docker-node1 ~]# docker rm -f busybox #删除运行的容器
[root@Docker-node1 ~]# docker container prune -f #删除所有停止的容器
2.2.5 容器内容提交
默认情况下,容器被删除后,在容器中的所有操作都会被清理,包括要保存的文件 如果想永久保存,那么我们需要把动作提交,提交后会生成新的镜像 当我们在运行新镜像后即可看到我们提交的内容
[root@Docker-node1 ~]# docker run -it --name test busybox
/ # touch leefile——在容器中建立文件
[root@Docker-node1 ~]# docker rm test
[root@Docker-node1 ~]# docker run -it --name test busybox——删除容器后开启新的容器文件不存在
/ # ls
[root@Docker-node1 ~]# docker commit -m "add leefile" test busybox:v1 sha256:c8ff62b7480c951635acb6064acdfeb25282bd0c19cbffee0e51f3902cbfa4bd
可以看到有一个添加好文件的镜像
[root@Docker-node1 ~]# docker images
查看历史操作
[root@Docker-node1 ~]# docker image history busybox:v1
此方法不利于企业审计,所以不推荐使用,在企业中我们多用Dockerfile来构建镜像
2.2.6 系统中的文件和容器中的文件传输
[root@Docker-node1 ~]# docker cp test2:/leefile /mnt #把容器中的文件复制到本机,test2表示容器名
Successfully copied 1.54kB to /mnt
[root@Docker-node1 ~]# docker cp /etc/fstab test2:/fstab #把本机文件复制到容器中
2.2.7 查询容器内部日志
[root@Docker-node1 ~]# docker logs web
三、Docker镜像构建
3.1docker镜像结构
共享宿主机的kernel
base镜像提供的是最小的Linux发行版
同一docker主机支持运行多种Linux发行版
采用分层结构的最大好处是:共享资源
3.2 镜像运行的基本原理
Copy-on-Write 可写容器层
容器层以下所有镜像层都是只读的
docker从上往下依次查找文件
容器层保存镜像变化的部分,并不会对镜像本身进行任何修改
一个镜像最多127层
3.4镜像构建
3.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 443 |
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功能和用法类似,但动作不可被覆盖 |
参数示例及用法:
[root@docker-node1 ~]# mkdir docker/
[root@docker-node1 ~]# cd docker/
[root@docker-node1 docker]# touch jclfile
[root@docker-node1 docker]# vim Dockerfile
下面编辑文件中ADD 参数后再加一个 /,不然要报错
[root@docker-node1 docker]# docker build -t example:v1 .
可以看到如果像以上文件中的那种写法会有两个警告
可以看到虽然有警告但还是可以运行
并且实行打印,但是CMD是可以被代替的,所以如果加一个sh就不会打印了
写法换成一下两条就可以看到没有警告
运行容器可以执行echo
如果将ENTRYPOINT注释打开,可以看到命令不可被覆盖
可以显示端口
通过这条命令可以看到该层镜像执行了哪些操作
[root@docker-node1 ~]# docker history example:v2
IMAGE CREATED CREATED BY SIZE COMMENT
94887e91180e 6 hours ago EXPOSE map[443/tcp:{} 80/tcp:{}] 0B buildkit.dockerfile.v0
<missing> 6 hours ago ENTRYPOINT ["/bin/sh" "-c" "echo hello jcl"] 0B buildkit.dockerfile.v0
<missing> 6 hours ago ENV name=jcl 0B buildkit.dockerfile.v0
<missing> 6 hours ago ADD jclfile.gz / # buildkit 0B buildkit.dockerfile.v0
<missing> 6 hours ago LABEL user=jcl 0B buildkit.dockerfile.v0
<missing> 6 hours ago COPY jclfile / # buildkit 0B buildkit.dockerfile.v0
<missing> 15 months ago BusyBox 1.36.1 (glibc), Debian 12 4.26MB
编辑Dockerfile 重新构建镜像注释ENTRYPOINT参数
运行容器会发现file1 file2是通过ADD从宿主机上上传并解压出来的,jclfile是通过copy复制过来的
VOLUME WORKDIR RUN参数
查看容器具体信息可以看到有挂载点
[root@docker-node1 docker]# docker inspect test
3.4.2Dockerfile实例
构建nginx镜像
将软件压缩包拖到mnt目录下
[root@docker-node1 mnt]# ll
total 373024
-rw-r--r-- 1 root root 211699200 Aug 27 22:23 centos-7.tar.gz
-rw-r--r-- 1 root root 22456832 Aug 27 22:23 debian11.tar.gz
drwxr-xr-x. 2 root root 6 Jul 31 15:22 hgfs
-rw-r--r-- 1 root root 146568704 Aug 27 22:23 nginx-1.23.tar.gz
-rw-r--r-- 1 root root 1244738 Aug 27 22:23 nginx-1.26.1.tar.gz
将系统镜像导入
[root@docker-node1 ~]# docker load -i /mnt/centos-7.tar.gz
174f56854903: Loading layer 211.7MB/211.7MB
挂载系统7镜像
[root@docker-node1 docker]# yum install httpd -y
[root@docker-node1 docker]# vim /etc/httpd/conf/httpd.conf——把端口改成8080防止端口冲突
[root@docker-node1 docker]# systemctl start httpd
[root@docker-node1 docker]# mkdir /var/www/html/rhel7.9
[root@docker-node1 docker]# mount /dev/sr1 /var/www/html/rhel7.9/
mount: /var/www/html/rhel7.9: WARNING: source write-protected, mounted read-only.
[root@docker-node1 docker]# docker run -it --name centos centos:7
[root@804ff4f252a7 /]# ls
anaconda-post.log dev home lib64 mnt proc run srv tmp var
bin etc lib media opt root sbin sys usr
[root@docker-node1 ~]# docker inspect centos
[root@docker-node1 yum.repos.d]# docker run -it --name centos centos:7
[root@804ff4f252a7 /]# cd /etc/yum.repos.d/
[root@804ff4f252a7 yum.repos.d]# ls
CentOS-Base.repo CentOS-Media.repo CentOS-fasttrack.repo
CentOS-CR.repo CentOS-Sources.repo CentOS-x86_64-kernel.repo
CentOS-Debuginfo.repo CentOS-Vault.repo
[root@804ff4f252a7 yum.repos.d]# rm -fr *
[root@804ff4f252a7 yum.repos.d]# ls
[root@804ff4f252a7 yum.repos.d]# vi rhel9.repo
[centos7]
name=centos7
baseurl=http://172.17.0.1:8080/rhel7.9
gpgcheck=0
[root@docker-node1 rhel9]# docker commit -m "add repo" centos centos:repo
sha256:6c2c6db9859754381fc7426aff152c55607d0a640dfa12316000cafc3b62a177
[root@docker-node1 rhel9]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos repo 6c2c6db98597 52 seconds ago 204MB
编辑构建文件
[root@docker-node1 docker]# mv /mnt/nginx-1.26.1.tar.gz .
[root@docker-node1 docker]# ls
Dockerfile nginx-1.26.1.tar.gz
[root@docker-node1 ~]# cd docker/
[root@docker-node1 docker]# vim Dockerfile
FROM centos::repo——用centos7基础系统镜像
ADD nginx-1.26.1.tar.gz /mnt——添加软件包在mnt下
WORKDIR /mnt/nginx-1.26.1——切换工作目录
RUN yum install -y gcc make pcre-devel openssl-devel——安装依赖
RUN ./configure --with-http_ssl_module --with-http_stub_status_module——编译
RUN make
RUN make install——源码安装
EXPOSE 80 443——暴露端口
VOLUME ["/usr/local/nginx/html"]——申明数据卷
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"——执行命令
[root@docker-node1 docker]# docker build -t nginx:v1 .
[root@docker-node1 docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v1 5085bb6de68f 22 seconds ago 356MB
3.5镜像优化方案
减少层或者多阶级建构或者使用最精简镜像
精简构建
[root@docker-node1 docker]# vim Dockerfile
FROM centos:repo
ADD nginx-1.26.1.tar.gz /mnt
WORKDIR /mnt/nginx-1.26.1
RUN yum install -y gcc make pcre-devel openssl-devel && ./configure --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -fr /mnt/nginx-1.26
.1 && yum clean all
EXPOSE 80 443
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
[root@docker-node1 docker]# docker build -t nginx:v2 .
多阶构建
[root@docker-node1 docker]# vim Dockerfile
FROM centos:repo AS build
ADD nginx-1.26.1.tar.gz /mnt
WORKDIR /mnt/nginx-1.26.1
RUN yum install -y gcc make pcre-devel openssl-devel && ./configure --with-http_ssl_module --with-http_stub_status_module && make && make install && rm -fr /mnt/nginx-1.26.1 && yum clean all
FROM centos:repo
LABEl mail=jcl@strongjcl.org
COPY --from=build /usr/local/nginx /usr/local/nginx
EXPOSE 80 443
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
构建会更快
[root@docker-node1 docker]# docker build -t nginx:v3 .
[root@docker-node1 docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v3 b9671a0763e2 41 seconds ago 210MB
nginx v2 96badad8af99 5 minutes ago 292MB
nginx v1 5085bb6de68f 13 minutes ago 356MB
最小精简镜像安装
查看必须调度的模块
[root@docker-node1 ~]# docker run -d --name test nginx:v4
3f04bc27690cabc4ba7456ceabf2fe12fce2cc81dfeeafb69c6707069ed61a13
[root@docker-node1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3f04bc27690c nginx:v4 "/usr/local/nginx/sb…" 41 seconds ago Up 41 seconds 80/tcp, 443/tcp test
[root@docker-node1 ~]# docker exec -it test sh
sh-4.2# ldd /usr/local/nginx/sbin/nginx
linux-vdso.so.1 => (0x00007ffdf73cd000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fbb71b4f000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fbb71933000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fbb716fc000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fbb7149a000)
libssl.so.10 => /lib64/libssl.so.10 (0x00007fbb71228000)
libcrypto.so.10 => /lib64/libcrypto.so.10 (0x00007fbb70dc5000)
libz.so.1 => /lib64/libz.so.1 (0x00007fbb70baf000)
libc.so.6 => /lib64/libc.so.6 (0x00007fbb707e1000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbb71d53000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007fbb705de000)
libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007fbb70391000)
libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007fbb700a8000)
libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007fbb6fea4000)
libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007fbb6fc71000)
libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007fbb6fa61000)
libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007fbb6f85d000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fbb6f643000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fbb6f41c000)
上传本地镜像
[root@docker-node1 ~]# docker load -i /mnt/debian11.tar.gz
[root@docker-node1 ~]# docker load -i /mnt/nginx-1.23.tar.gz
[root@docker-node1 docker]# vim Dockerfile
FROM nginx:latest as base
RUN mkdir -p /opt/var/cache/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
FROM gcr.io/distroless/base-debian11
COPY --from=base /opt /
EXPOSE 80 443
ENTRYPOINT ["nginx", "-g", "daemon off;"]
[root@docker-node1 docker]# docker build -t nginx:v4 .
[root@docker-node1 docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v4 cb1503f9fa44 6 seconds ago 34.5MB
nginx v3 8a71114560af 37 minutes ago 210MB
nginx v2 96badad8af99 42 minutes ago 292MB
nginx v1 5085bb6de68f 50 minutes ago 356MB
centos repo 6c2c6db98597 55 minutes ago 204MB
nginx latest 5ef79149e0ec 12 days ago 188MB
nginx 1.23 a7be6198544f 15 months ago 142MB
busybox latest 65ad0d468eb1 15 months ago 4.26MB
centos 7 eeb6ee3f44bd 2 years ago 204MB
timinglee/game2048 latest 19299002fdbe 7 years ago 55.5MB
timinglee/mario latest 9a35a9e43e8c 8 years ago 198MB
gcr.io/distroless/base-debian11 latest 2a6de77407bf N/A 20.6MB
运行容器
[root@docker-node1 ~]# docker run --rm -d --name test nginx:v4
[root@docker-node1 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1e8fc10301f nginx:v4 "nginx -g 'daemon of…" About a minute ago Exited (0) About a minute ago test
四、镜像仓库的管理
4.1 什么是docker仓库
Docker 仓库(Docker Registry) 是用于存储和分发 Docker 镜像的集中式存储库。 它就像是一个大型的镜像仓库,开发者可以将自己创建的 Docker 镜像推送到仓库中,也可以从仓库中拉 取所需的镜像。
Docker 仓库可以分为公共仓库和私有仓库:
公共仓库,如 Docker Hub,任何人都可以访问和使用其中的镜像。许多常用的软件和应用都有在 Docker Hub 上提供的镜像,方便用户直接获取和使用。例如,您想要部署一个 Nginx 服务器,就可以从 Docker Hub 上拉取 Nginx 的镜像。
私有仓库则是由组织或个人自己搭建和管理的,用于存储内部使用的、不希望公开的镜像。 比如,一家企业为其特定的业务应用创建了定制化的镜像,并将其存储在自己的私有仓库中, 以保证安全性和控制访问权限。
通过 Docker 仓库,开发者能够方便地共享和复用镜像,加速应用的开发和部署过程。
4.2 docker hub
官网: https://hub.docker.com/
Docker Hub 是 Docker 官方提供的一个公共的镜像仓库服务。
它是 Docker 生态系统中最知名和广泛使用的镜像仓库之一,拥有大量的官方和社区贡献的镜像。 以下是 Docker Hub 的一些关键特点和优势:
1. 丰富的镜像资源:涵盖了各种常见的操作系统、编程语言运行时、数据库、Web 服务器等众多应用 的镜像。 例如,您可以轻松找到 Ubuntu、CentOS 等操作系统的镜像,以及 MySQL、Redis 等数据库 的镜像。
2. 官方支持:提供了由 Docker 官方维护的一些重要镜像,确保其质量和安全性。
3. 社区贡献:开发者们可以自由上传和分享他们创建的镜像,促进了知识和资源的共享。
4. 版本管理:对于每个镜像,通常都有多个版本可供选择,方便用户根据需求获取特定版本。
5. 便于搜索:用户可以通过关键词轻松搜索到所需的镜像。
4.3搭建docker的私有仓库
4.3.1搭建registry仓库
拉取和上传都需要用户认证,index是主要的角色
导入镜像
这里加载的都是本地镜像,需要的话文章开头获取
[root@docker-node1 ~]# docker load -i registry.tag.gz
ce7f800efff9: Loading layer 7.644MB/7.644MB
30609d4f10dd: Loading layer 792.6kB/792.6kB
3b6a51496c9d: Loading layer 17.55MB/17.55MB
e704e9e3e9dc: Loading layer 3.584kB/3.584kB
f019f591461d: Loading layer 2.048kB/2.048kB
Loaded image: registry:latest
开启registry
[root@docker-node1 ~]# docker run -d -p 5000:5000 --restart=always registry
0bdff94ec6f1aff6d2f39a2ec1b70ba4b4d47ca14ac1c6dc24667364108bd68b
查看容器开启信息
[root@docker-node1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0bdff94ec6f1 registry "/entrypoint.sh /etc…" 12 seconds ago Up 11 second s 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp crazy_hugle
设置非加密上传
[root@docker-node1 ~]# vim /etc/docker/daemon.json
{
"insecure-registries" : ["http://172.25.254.100:5000"]
}
重启
[root@docker-node1 ~]# systemctl restart docker
给需要上传的镜像设置一个索引
[root@docker-node1 ~]# docker tag nginx:v4 172.25.254.100:5000/nginx:v4
[root@docker-node1 ~]# docker push 172.25.254.100:5000/nginx:v4
The push refers to repository [172.25.254.100:5000/nginx]
c713dc774b96: Pushed
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
v4: digest: sha256:c19522dcc528d9ee8c55875e3adaef451b5bfccee1ec2ce8a0aa27551975a252 size: 3445
查看是否上传成功
[root@docker-node1 ~]# curl 172.25.254.100:5000/v2/_catalog
{"repositories":["nginx"]}
4.3.2为registry提加密传输
加密传输
清空json文件重启
[root@docker-node1 ~]# vim /etc/docker/daemon.json
[root@docker-node1 ~]# systemctl restart docker
创建一个存放证书的目录
[root@docker-node1 ~]# mkdir certs
先给本机添加一个域名解析
[root@docker-node1 ~]# vim /etc/hosts
172.25.254.100 docker-node1.jcl.org reg.jcl.org
生成认证key和证书
[root@docker-node1 ~]# openssl req -newkey rsa:4096 \
-nodes -sha256 -keyout certs/jcl.org.key \
-addext "subjectAltName = DNS:reg.jcl.org" \ ——指定备用名称和添加的域名要相同
-x509 -days 365 -out certs/jcl.org.crt
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:Shichuan
Locality Name (eg, city) [Default City]:chengdu
Organization Name (eg, company) [Default Company Ltd]:docker
Organizational Unit Name (eg, section) []:registry
Common Name (eg, your name or your server's hostname) []:reg.jcl.org
Email Address []:
[root@docker-node1 ~]# ls certs/
jcl.org.crt jcl.org.key
启动registry仓库
[root@docker-node1 ~]# docker run -d -p 443:443 --restart=always --name registry --name registry -v /opt/registry:/var/lib/registry -v /root/certs:/certs \
> -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
> -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/jcl.org.crt \
> -e REGISTRY_HTTP_TLS_KEY=/certs/jcl.org.key registry:latest
d25d163025213709c33a08ca9d3fc8a4d13bba3ba84b1cb9076912eab8a91d73
[root@docker-node1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d25d16302521 registry:latest "/entrypoint.sh /etc…" About a minute ago Up About a minute 0.0.0.0:443->443/tcp, :::443->443/tcp, 5000/tcp registry
测试:
[root@docker-node1 ~]# docker tag nginx:v4 reg.jcl.org/nginx:v4——添加别名
[root@docker-node1 ~]# docker push reg.jcl.org/nginx:v4——docker客户端没有证书拉取失败
The push refers to repository [reg.jcl.org/nginx]
Get "https://reg.jcl.org/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority
为客户端建立证书
[root@docker-node1 ~]# mkdir -p /etc/docker/certs.d/reg.jcl.org -p
[root@docker-node1 ~]# cp /root/certs/jcl.org.crt /etc/docker/certs.d/reg.jcl.org/ca.crt
[root@docker-node1 ~]# ls /etc/docker/certs.d/reg.jcl.org/ca.crt
/etc/docker/certs.d/reg.jcl.org/ca.crt
再次拉取就可以啦
[root@docker-node1 ~]# docker push reg.jcl.org/nginx:v4 The push refers to repository [reg.jcl.org/nginx]
c713dc774b96: Pushed
6835249f577a: Pushed
24aacbf97031: Pushed
8451c71f8c1e: Pushed
2388d21e8e2b: Pushed
4.3.3建立登录认证
安装建立认证文件的工具包
[root@docker-node1 ~]# yum install httpd-tools -y
建立认证文件
[root@docker-node1 ~]# mkdir auth
[root@docker-node1 ~]# htpasswd -Bc auth/htpasswd jcl——-B强制使用最安全的加密方式,默认用md5加密
New password:
Re-type new password:
Adding password for user jcl
[root@docker-node1 ~]# cat auth/htpasswd
jcl:$2y$05$usYpfYOckRzgDM.nktxdcuZzTuYL4GwUIzvs8i.MFADNVi6fAiQ1a
[root@docker-node1 ~]# htpasswd -B auth/.htpasswd zly——增加第二用户
New password:
Re-type new password:
Adding password for user zly
[root@docker-node1 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d25d16302521 registry:latest "/entrypoint.sh /etc…" 15 minutes ago Up 15 minutes 0.0.0.0:443->443/tcp, :::443->443/tcp, 5000/tcp registry
[root@docker-node1 ~]# docker rm -f registry——将之前的同名容器删掉
registry
添加认证到registry容器中
[root@docker-node1 ~]# docker run -d -p 443:443 --restart=always --name registry \
--name registry -v /opt/registry:/var/lib/registry -v /root/certs:/certs \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/jcl.org.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/jcl.org.key \
-v /root/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \——这里的目录一定要与你的认证用户目录相同
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \——用户登录密码目录
registry:latest
0273a8d12b8048376383907fde6f22bf9a9be5db35c0cbca256087748ae55a06
测试:
[root@docker-node1 ~]# docker tag nginx:v3 reg.jcl.org/nginx:v3
这时直接上传会报错因为需要用户登录
[root@docker-node1 ~]# docker push reg.jcl.org/nginx:v3
The push refers to repository [reg.jcl.org/nginx]
e9cbde3d3c79: Preparing
eb115aa3ecc5: Preparing
174f56854903: Preparing
no basic auth credentials
登录用户
[root@docker-node1 ~]# docker login reg.jcl.org
再次上传就可以啦
[root@docker-node1 ~]# docker push reg.jcl.org/nginx:v3
查看信息也是需要指定用户登录
[root@docker-node1 ~]# curl -k https://172.25.254.100/v2/_catalog
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}
[root@docker-node1 ~]# curl -k https://172.25.254.100/v2/_catalog -ujcl:jcl
{"repositories":["nginx"]}
4.4构建企业级私有仓库HARBOR
4.4.1部署harbor
[root@docker-node1 ~]# cd harbor/
[root@docker-node1 harbor]# cp harbor.yml.tmpl harbor.yml
[root@docker-node1 harbor]# ls
common.sh harbor.yml install.sh prepare
harbor.v2.5.4.tar.gz harbor.yml.tmpl LICENSE
[root@docker-node1 harbor]# vim harbor.yml
hostname: reg.jcl.org
certificate: /data/certs/jcl.org.crt
private_key: /data/certs/jcl.org.key
harbor_admin_password: 123
[root@docker-node1 harbor]# mkdir /data
这里的证书文件在上节安装registry生成的
[root@docker-node1 certs]# cp /root/certs /data/ -r
[root@docker-node1 certs]# ll
total 8
-rw-r--r-- 1 root root 2078 Aug 28 15:17 jcl.org.crt
-rw------- 1 root root 3272 Aug 28 15:17 jcl.org.key
安装harbor
[root@docker-node1 harbor]# ./install.sh --with-chartmuseum
安装完测试访问一下
4.4.2管理仓库
新建一个项目名叫strongjcl
然后就可以登录进去上传镜像了
[root@docker-node1 harbor]# docker tag nginx:v4 reg.jcl.org/strongjcl/nginx:v4
[root@docker-node1 harbor]# docker logout reg.jcl.org
Removing login credentials for reg.jcl.org
[root@docker-node1 harbor]# docker login reg.jcl.org
Username: admin
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-node1 harbor]# docker push reg.jcl.org/strongjcl/nginx:v4
然后就可以看到我们上传的镜像
五、Docker网络
5.1 docker原生bridge网路
将我们本机设置成iptables
[root@docker-node1 ~]# grubby --update-kernel ALL --args iptables=true
[root@docker-node1 ~]# reboot
关闭harbor
[root@docker-node1 harbor]# docker compose down
[root@docker-node1 harbor]# docker network ls
5.2 docker原生网络host
[root@docker-node1 ~]# docker run -it --rm --name test --network host busybox
/ # ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:CB:68:D1:71
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
5.3 docker 原生网络none
[root@docker-node1 ~]# docker run -it --rm --name test --network none busybox
/ # 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)
5.4 docker的自定义网络
5.4.1自定义桥接
如果是默认的桥接网络,没有域名解析功能
[root@docker-node1 ~]# docker run -it --name test --network bridge
/ # ping test1
ping: bad address 'test1'
如果是自定义桥接网络的话
[root@docker-node1 ~]# docker network create mybridge -d bridge
7a97b6af4b63771526fe2b718d33318d69e87d6474dd819e7043c93d71d3cca5
[root@docker-node1 ~]# docker network ls
7a97b6af4b63 mybridge bridge local
[root@docker-node1 ~]# docker run -it --name test1 --network mybridge busybox
/ #
[root@docker-node1 ~]# docker run -it --name test --network mybridge busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:12:00:03
inet addr:172.18.0.3 Bcast:172.18.255.255 Mask:255.255.0.0
测试可以ping通test1
5.4.2 如何让不同的自定义网络互通?
创建不同网站的ip
[root@docker-node1 ~]# docker network create mynet1 -d bridge
busybox
20da3f406020a3ed75334ecf9354ee46b5fd83a013c3d63908545c3d80b5259f
[root@docker-node1 ~]# docker network create mynet2 -d bridge
8da3c1e2b92b3d0985b7c039f17aeef47701e23b5e7b7136fe4b8a1462dca4e1
[root@docker-node1 ~]# docker network ls
[root@docker-node1 ~]# iptables -t nat -nL
[root@docker-node1 ~]# docker run -it --name test1 --network mynet2 busybox
[root@docker-node1 ~]# docker run -it --name test --network mynet1 busybox
给test1容器再加一个网卡,ip为test上连接的ip就可以互通
[root@docker-node1 ~]# docker network connect mynet1 test1
5.4.3 joined容器网络
joined网络示例演示
[root@docker-node1 ~]# docker run -it --name test --network mynet1 busybox
[root@docker-node1 ~]# docker run -it --name test1 --network container:test busybox
[root@docker-node1 ~]# docker rm -f test test1
test
test1
利用容器部署phpmyadmin管理mysql
[root@docker-node1 ~]# docker load -i phpmyadmin-latest.tar.gz
[root@docker-node1 ~]# docker load -i mysql-5.7.tar.gz
[root@docker-node1 ~]# docker run -d --name mysqladmin --network mynet1 \
> -e PMA_ARBITRARY=1 \
> -p 80:80 phpmyadmin:latest
bdd9f7da56ef9df2552a0e1aaaaddae037138ec7d8aa0ddfa0c5306f702084b5
[root@docker-node1 ~]# docker run -d --name mysql --network container:mysqladmin -e MYSQL_ROOT_PASSWORD='123' mysql:5.7
11aad252fc67ba791dd1ad206d73c47bbebd7d2d4daafdac0f6a3ce458eceb7c
通过浏览器访问
添加数据
[root@docker-node1 ~]# docker exec -it mysql mysql -uroot -p123
5.5. 容器内外网的访问
5.5.1 容器访问外网
[root@docker ~]# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE 6 -- 172.17.0.2 172.17.0.2 tcp dpt:80
内网访问外网策略
Chain DOCKER (0 references) destination
target prot opt source
DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
to:172.17.0.2:80
5.5.2 外网访问docker容器
端口映射 -p 本机端口:容器端口来暴漏端口从而达到访问效果
[root@docker ~]# docker run -d --name webserver -p 80:80 nginx
[root@docker ~]# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE 6 -- 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (0 references) destination
target prot opt source
DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
to:172.17.0.2:80
外网访问内网策略
5.6 docker跨主机网络
macvlan网络方式实现跨主机通信
在两台docker主机上各添加一块网卡,打开网卡混杂模式:
eth1为仅主机模式
[root@docker-node1 ~]# ip link set eth1 promisc on
[root@docker-node1 ~]# ifconfig eth1
eth1: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>#####后面内容忽略
添加macvlan网络
[root@docker-node1 ~]# docker network create -d macvlan \
> --subnet 4.4.4.0/24 \
> --gateway 4.4.4.4 \
> -o parent=eth1 mynet1
[root@docker-node1 ~]# docker run -it --rm --name test --network mynet1 busybox
[root@docker-node2 ~]# docker run -it --rm --name test --network mynet1 --ip 4.4.4.2 busybox
在node2上ping node1
如果关掉node1上的容器就ping不通了
六、Docker 数据卷管理及优化
6.1 为什么要用数据卷
docker数据卷
mount到主机中,绕开分层文件系统
和主机磁盘性能相同,容器删除后依然保留
仅限本地磁盘,不能随容器迁移
docker提供了两种卷:
bind mount docker
managed volume
6.2 bind mount 数据卷
bind mount
[root@docker-node1 ~]# mkdir /jcl
[root@docker-node1 /]# touch /jcl/jcl-file{1..5}
[root@docker-node1 /]# ls /jcl
jcl-file1 jcl-file2 jcl-file3 jcl-file4 jcl-file5
[root@docker-node1 /]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@docker-node1 /]# docker run -it --rm --name test -v /jcl:/data1:rw -v /etc/passwd:/data2/passwd busybox
/ # ls
bin data2 etc lib proc sys usr
data1 dev home lib64 root tmp var
/ # ls data1
jcl-file1 jcl-file2 jcl-file3 jcl-file4 jcl-file5
/ # ls data2
passwd
/ # touch data1/jcl-file6
/ # [root@docker-node1 ls /jcl/
jcl-file1 jcl-file2 jcl-file3 jcl-file4 jcl-file5 jcl-file6
6.3 docker managed 数据卷
bind mount必须指定host文件系统路径,限制了移植性
docker managed volume 不需要指定mount源,docker自动为容器创建数据卷目录
默认创建的数据卷目录都在 /var/lib/docker/volumes 中
如果挂载时指向容器内已有的目录,原有数据会被复制到volume中
[root@docker volumes]# docker run -d --name mysql -e MYSQL_ROOT_PASSWORD='lee' mysql:5.7
[root@docker volumes]# ls -l /var/lib/docker/volumes
[root@docker volumes]# touch ad74662b8d6bb6fdcc6e82925ae9942b94bac5f9da4bd52b0a14ac451ae9ef75/_data/leefile
[root@docker volumes]# docker exec -it mysql bash
bash-4.2# cd /var/lib/mysql
bash-4.2# ls
清理未使用的数据卷,不可逆操作
[root@docker ~]# docker volume prune
建立数据卷
[root@docker ~]# docker volume create jcl1
[root@docker ~]# ls -l /var/lib/docker/volumes/jcl1/_data/
查看卷
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local jcl1
使用建立的数据卷
[root@docker _data]# docker run -d --name web1 -p 80:80 -v jcl1:/usr/share/nginx/html nginx
[root@docker _data]# cd /var/lib/docker/volumes/jcl1/_data
[root@docker _data]# echo jcl1 > index.html
[root@docker _data]# curl 172.25.254.100
jcl1
6.4 数据卷容器(Data Volume Container)
数据卷容器(Data Volume Container)是 Docker 中一种特殊的容器,主要用于方便地在多个容器之间 共享数据卷。
建立数据卷容器
[root@docker ~]# docker run -d --name datavol \
-v /tmp/data1:/data1:rw \
-v /tmp/data2:/data2:ro \
-v /etc/resolv.conf:/etc/hosts busybox
使用数据卷容器
[root@docker ~]# docker run -it --name test --rm --volumes-from datavol busybox
6.5 bind mount 数据卷和docker managed 数据卷的对 比
相同点: 两者都是 host 文件系统中的某个路径
不同点:
bind mount | docker managed volume | |
volume位置 | 可任意指定 | /var/lib/docker/volumes/.. |
对已有的挂载点的影响 | 隐藏并替换volume | 原有数据复制到volume |
是否支持单个文件 | 支持 | 不支持只能是目录 |
权限控制 | 可设置为只读,默认为读写权限 | 无控制,均为读写权限 |
移植性 | 移植性弱,与host path绑定 | 移植性强。无需指定host目录 |
6.6 备份与迁移数据卷
备份数据卷
#建立容器并指定使用卷到要备份的容器
[root@docker ~]# docker run --volumes-from datavol \
-v `pwd`:/backup busybox \ #把当前目录挂在到容器中用于和容器交互保存要备份的容器
tar zcf /backup/data1.tar.gz /data1 #备份数据到本地
数据恢复
[root@docker ~]#docker run -it --name test -v leevol1:/data1 -v `pwd`:/backup busybox /bin/sh c "tar zxf /backup/data1.tar.gz;/bin/sh"
七、Docker 的安全优化
在rhel9中默认使用cgroup-v2 但是cgroup-v2中不利于观察docker的资源限制情况,所以推荐使用 cgroup-v1
[root@docker-node1 /]# grubby --update-kernel=/boot/vmlinuz-$(uname -r) \
> --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
重启系统reboot
1、命名空间隔离的安全
命名空间隔离
[root@docker-node1 ~]# docker run -d --name web nginx
540b22197db5814703eaa7630a1e03ce20d385cb8e23495f1c1fc565ef143379
[root@docker-node1 ~]# docker inspect web | grep Pid
"Pid": 4049,
"PidMode": "",
"PidsLimit": null,
[root@docker-node1 ~]# cd /proc/4049/ns
[root@docker-node1 ns]# ls
cgroup ipc mnt net pid pid_for_children time time_for_children user uts
[root@docker-node1 ns]# ls -d /sys/fs/cgroup/memory/docker/540b22197db5814703eaa7630a1e03ce20d385cb8e23495f1c1fc565ef143379
/sys/fs/cgroup/memory/docker/540b22197db5814703eaa7630a1e03ce20d385cb8e23495f1c1fc565ef143379
2、控制组资源控制的安全
控制组资源控制的安全
[root@docker-node1 ns]# docker run -it --name test busybox
/ # free -m
total used free shared buff/cache available
Mem: 1744 655 383 16 706 924
Swap: 2048 0 2048
[root@docker-node1 ~]# free -m
total used free shared buff/cache available
Mem: 1743 759 442 15 705 983
Swap: 2047 0 2047
3、内核能力机制
能力机制(Capability)是Linux内核一个强大的特性,可以提供细粒度的权限访问控制。
大部分情况下,容器并不需要“真正的”root权限,容器只需要少数的能力即可。
默认情况下,Docker采用“白名单”机制,禁用“必需功能”之外的其他权限。
4、Docker服务端防护
使用Docker容器的核心是Docker服务端,确保只有可信的用户才能访问到Docker服务。 2719
将容器的root用户映射到本地主机上的非root用户,减轻容器和主机之间因权限提升而引起的安全 问题。
允许Docker 服务端在非root权限下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。 这些子进程只允许在特定范围内进行操作。
7.1 Docker的资源限制
Linux Cgroups 的全称是 Linux Control Group。
是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。
对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。
Linux Cgroups 给用户暴露出来的操作接口是文件系统
它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
执行此命令查看:mount -t cgroup
[root@docker-node1 ~]# mount -t cgroup
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/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/misc type cgroup (rw,nosuid,nodev,noexec,relatime,misc)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
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/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
7.1.1.限制cpu使用
1.限制cpu的使用量
[root@docker-node1 ~]# docker run -it --rm --name test \
> --cpu-period 100000 \
> --cpu-quota 20000 ubuntu
root@cdc70ba47b4b:/# dd if=/dev/zero of=/dev/null &
[1] 9
root@cdc70ba47b4b:/# top
top - 08:16:12 up 1:12, 0 user, load average: 0.01, 0.11, 0.08
Tasks: 3 total, 2 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 7.0 us, 14.3 sy, 0.0 ni, 78.4 id, 0.0 wa, 0.3 hi, 0.0 si, 0.0 st
MiB Mem : 1743.7 total, 253.4 free, 846.6 used, 811.7 buff/cache
MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 897.1 avail Mem
查看
[root@docker-node1 docker]# cat /sys/fs/cgroup/cpu,cpuacct/docker/cdc70ba47b4b42deb190c3cba9ecd274e527c9b4692c43c76d3adec70a2c2e76/cpu.cfs_quota_us
20000
2.限制cpu的优先级
因为只有cpu都在被使用的状态下才会有cpu抢占的情况,所以我们要关掉其他内核或者只用一个内核
[root@docker-node1 ~]# echo 0 > /sys/devices/system/cpu/cpu1/online
开启一个容器设置cpu优先级为100
[root@docker-node1 ~]# docker run -it --rm --cpu-shares 100 ubuntu
root@9a3d814aa39e:/# dd if=/dev/zero of=/dev/null &
[1] 9
root@9a3d814aa39e:/# top
再开启一个容器不设置优先级默认为1024
[root@docker-node1 docker]# docker run -it --rm ubuntu
root@b02ef3a4cd83:/# dd if=/dev/zero of=/dev/null &
[1] 9
root@b02ef3a4cd83:/# top
可以看到第一个设置优先级的容器占用cpu的情况
7.1.2 限制内存使用
[root@docker-node1 ~]# docker run -d --name test --memory 200M --memory-swap 200M nginx
d7b9008fa3052db7c85d5fddd8bafc05d0940b7dd647f06d31532d5901f3604d
[root@docker-node1 docker]# cd /sys/fs/cgroup/memory/docker/d7b9008fa3052db7c85d5fddd8bafc05d0940b7dd647f06d31532d5901f3604d
[root@docker-node1 d7b9008fa3052db7c85d5fddd8bafc05d0940b7dd647f06d31532d5901f3604d]# cat memory.limit_in_bytes
209715200
[root@docker-node1 d7b9008fa3052db7c85d5fddd8bafc05d0940b7dd647f06d31532d5901f3604d]# cat memory.memsw.limit_in_bytes
209715200
[root@docker-node1 ~]# umount /rhel9
[root@docker-node1 ~]# mount /dev/sr1 /rhel9
[root@docker-node1 rhel9]# yum install *.rpm -y
测试:
[root@docker-node1 3c5fea83891da37ad56bbf91e7ea1e2cd37a5a8c2e5175689b6a747b8acef0a0]# cgexec -g memory:docker/3c5fea83891da37ad56bbf91e7ea1e2cd37a5a8c2e5175689b6a747b8acef0a0/ dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=100
[root@docker-node1 3c5fea83891da37ad56bbf91e7ea1e2cd37a5a8c2e5175689b6a747b8acef0a0]# cgexec -g memory:docker/3c5fea83891da37ad56bbf91e7ea1e2cd37a5a8c2e5175689b6a747b8acef0a0/ dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=200
自建控制器
[root@docker-node1 ~]# mkdir -p /sys/fs/cgroup/memory/x1/
[root@docker-node1 ~]# ls /sys/fs/cgroup/memory/x1
cgroup.clone_children memory.memsw.failcnt
cgroup.event_control memory.memsw.limit_in_bytes
cgroup.procs memory.memsw.max_usage_in_bytes
memory.failcnt memory.memsw.usage_in_bytes
memory.force_empty memory.move_charge_at_immigrate
memory.kmem.failcnt memory.numa_stat
memory.kmem.limit_in_bytes memory.oom_control
memory.kmem.max_usage_in_bytes memory.pressure_level
memory.kmem.slabinfo memory.soft_limit_in_bytes
memory.kmem.tcp.failcnt memory.stat
memory.kmem.tcp.limit_in_bytes memory.swappiness
memory.kmem.tcp.max_usage_in_bytes memory.usage_in_bytes
memory.kmem.tcp.usage_in_bytes memory.use_hierarchy
memory.kmem.usage_in_bytes notify_on_release
memory.limit_in_bytes tasks
memory.max_usage_in_bytes
[root@docker-node1 ~]# echo 209715200 > /sys/fs/cgroup/memory/x1/memory.limit_in_bytes
[root@docker-node1 ~]# cat /sys/fs/cgroup/memory/x1/tasks
[root@docker-node1 ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0.0882283 s, 1.2 GB/s
[root@docker-node1 ~]# free -m
total used free shared buff/cache available
Mem: 1743 942 175 114 899 801
Swap: 2047 1 2046
[root@docker-node1 ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=300
300+0 records in
300+0 records out
314572800 bytes (315 MB, 300 MiB) copied, 0.232134 s, 1.4 GB/s
[root@docker-node1 ~]# free -m
total used free shared buff/cache available
Mem: 1743 1042 75 204 989 701
Swap: 2047 112 1935
[root@docker-node1 ~]# rm -fr /dev/shm/bigfile
[root@docker-node1 ~]# echo 209715200 > /sys/fs/cgroup/memory/x1/memory.memsw.limit_in_bytes
[root@docker-node1 ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=200
Killed
[root@docker-node1 ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=199
Killed
[root@docker-node1 ~]# rm -fr /dev/shm/bigfile
[root@docker-node1 ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=180
180+0 records in
180+0 records out
188743680 bytes (189 MB, 180 MiB) copied, 0.0426276 s, 4.4 GB/s
[root@docker-node1 ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=190
190+0 records in
190+0 records out
199229440 bytes (199 MB, 190 MiB) copied, 0.0434641 s, 4.6 GB/s
[root@docker-node1 ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=200
Killed
7.1.3 限制docker的磁盘io
查看系统指定的磁盘目录
[root@docker-node1 ~]# fdisk -l
[root@docker-node1 ~]# docker run -it --rm \
> --device-write-bps \
> /dev/nvme0n1:30M \
> ubuntu
root@c393116572d9:/# dd if=/dev/zero of=bigfile
dd: writing to 'bigfile': No space left on device
8779305+0 records in
8779304+0 records out
4495003648 bytes (4.5 GB, 4.2 GiB) copied, 9.86951 s, 455 MB/s
root@c393116572d9:/# dd if=/dev/zero of=bigfile bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0.0368539 s, 2.8 GB/s
root@c393116572d9:/# dd if=/dev/zero of=bigfile bs=1M count=100 oflag=direct#设定dd命令直接写入磁盘
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 2.53646 s, 41.3 MB/s
7.2 Docker的安全加固
7.2.1 解决Docker的默认隔离性
[root@docker-node1 ~]# cd /mnt
[root@docker-node1 mnt]# ls
centos-7.tar.gz lxcfs-5.0.4-1.el9.x86_64.rpm nginx-1.23.tar.gz
debian11.tar.gz lxc-libs-4.0.12-1.el9.x86_64.rpm
hgfs lxc-templates-4.0.12-1.el9.x86_64.rpm
[root@docker-node1 mnt]# dnf install lxc*.rpm -y
运行lxcfs并解决容器隔离性
[root@docker-node1 mnt]# lxcfs /var/lib/lxcfs &
[root@docker-node1 mnt]# 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@037417559d41:/# free -m
7.2.2 容器特权
可以看到没有添加特权的时候是不能添加ip的
添加特权后就可以为eth0添加ip
[root@docker-node1 ~]# docker run -it --rm --name test --privileged busybox
但同时也可以为磁盘分区,会更改我们真实宿主机的设置
7.2.3 容器特权的白名单
所以为了避免这个情况我们可以单独为某个操作增加特权
[root@docker-node1 ~]# docker run --rm -it --name test --cap-add NET_ADMIN busybox
八 、容器编排工具Docker Compose
8.1 Docker Compose 概述
Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。 其是官方的一个开源项目,托管到github上
主要功能
1. 定义服务: 使用 YAML 格式的配置文件来定义一组相关的容器服务。每个服务可以指定镜像、端口映射、 环境变量、存储卷等参数。 例如,可以在配置文件中定义一个 Web 服务和一个数据库服务,以及它们之间的连接关系。
2. 一键启动和停止: 通过一个简单的命令,可以启动或停止整个应用程序所包含的所有容器。这大大简化了多容器 应用的部署和管理过程。 例如,使用 docker-compose up 命令可以启动配置文件中定义的所有服务,使用 compose down 命令可以停止并删除这些服务。
3. 服务编排: docker 可以定义容器之间的依赖关系,确保服务按照正确的顺序启动和停止。例如,可以指定数据库 服务必须在 Web 服务之前启动。 支持网络配置,使不同服务的容器可以相互通信。可以定义一个自定义的网络,将所有相关的 容器连接到这个网络上。
4. 环境变量管理: 可以在配置文件中定义环境变量,并在容器启动时传递给容器。这使得在不同环境(如开发、 测试和生产环境)中使用不同的配置变得更加容易。 例如,可以定义一个数据库连接字符串的环境变量,在不同环境中可以设置不同的值。
工作原理
1. 读取配置文件: Docker Compose 读取 YAML 配置文件,解析其中定义的服务和参数。
2. 创建容器: 根据配置文件中的定义,Docker Compose 调用 Docker 引擎创建相应的容器。它会下载所需 的镜像(如果本地没有),并设置容器的各种参数。
3. 管理容器生命周期: Docker Compose 监控容器的状态,并在需要时启动、停止、重启容器。 它还可以处理容器的故障恢复,例如自动重启失败的容器。
Docker Compose 中的管理层
1. 服务 (service) 一个应用的容器,实际上可以包括若干运行相同镜像的容器实例
2. 项目 (project) 由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中 定义
3. 容器(container)容器是服务的具体实例,每个服务可以有一个或多个容器。容器是基于服务定义 的镜像创建的运行实例
8.2 Docker Compose 的常用命令参数
1、服务管理
[root@docker-node1 ~]# vim ~/.vimrc
set ts=2 sw=2 ai et
[root@docker-node1 ~]# mkdir test
[root@docker-node1 ~]# cd test
[root@docker-node1 test]# vim docker-compose.yml
services:
web:
image: nginx:latest
ports:
- "80:80"
testnode:
image: busybox:latest
command: ["/bin/sh","-c","sleep 100000"]
[root@docker-node1 test]# docker compose up -d
[root@docker-node1 test]# docker compose ps
stop和down的区别就是前者不会删除容器,后者要删除
[root@docker-node1 test]# docker compose stop
[root@docker-node1 test]# docker compose start
[root@docker-node1 test]# docker compose down
[root@docker-node1 test]# docker compose ps
如果不是默认的命名方式则需要-f指定识别的文件名
且执行命令只能在yml脚本所在目录下执行
[root@docker-node1 test]# mv docker-compose.yml jcl.yml
[root@docker-node1 test]# ls
jcl.yml
[root@docker-node1 test]# docker compose up -d
no configuration file provided: not found
[root@docker-node1 test]# docker compose -f jcl.yml up -d
2、服务状态查看
[root@docker-node1 test]# docker compose -f jcl.yml ps
[root@docker-node1 test]# docker compose -f jcl.yml logs web
3、构建和重新构建服务
[root@docker-node1 test]# cd /root/docker
[root@docker-node1 docker]# vim Dockerfile
[root@docker-node1 docker]# cp Dockerfile Dockerfile.bak
[root@docker-node1 docker]# rm -f Dockerfile
[root@docker-node1 docker]# vim Dockerfile
FROM busybox:latest
RUN touch /jcl-file1
[root@docker-node1 docker]# vim jcl
FROM busybox:latest
RUN touch /jcl-file2
[root@docker-node1 docker]# cd /root/test
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: test1 #构建镜像的名字
build:
context: /root/docker #构建文件所在目录
dockerfile: Dockerfile
command: ["/bin/sh","-c","sleep 100000"]
container_name: jcl1 #指定容器名字
test2:
image: test2
build:
context: /root/docker
dockerfile: jcl
command: ["/bin/sh","-c","sleep 100000"]
container_name: jcl2
[root@docker-node1 test]# docker compose up -d
[root@docker-node1 test]# docker compose ps
也可以将构建镜像和启动容器分离开运行
[root@docker-node1 test]# docker compose down
[+] Running 3/3
✔ Container jcl1 Removed 10.2s
✔ Container jcl2 Removed 10.1s
✔ Network test_default Removed 0.1s
[root@docker-node1 test]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test1 latest 41d7dea7a0b0 About a minute ago 4.26MB
test2 latest c65476399bd3 About a minute ago 4.26MB
[root@docker-node1 test]# docker rmi test1 test2
Untagged: test1:latest
Deleted: sha256:41d7dea7a0b02ed545634715aafeaa1373dc37cca894b231c28fce3d237f1a93
Untagged: test2:latest
先构建镜像
[root@docker-node1 test]# docker compose build
[root@docker-node1 test]# docker compose build test1
也可以单独只构建一个
[root@docker-node1 test]# docker compose build test2
再启动容器
[root@docker-node1 test]# docker compose up -d
[+] Running 3/3
✔ Network test_default Created 0.1s
✔ Container jcl1 Started 0.6s
✔ Container jcl2 Started
4、其他操作
在容器运行时执行命令
[root@docker-node1 test]# docker compose exec -it test1 /bin/sh
从仓库中拉取镜像
[root@docker-node1 test]# docker compose pull
验证yml文件是否有错,没有会进行内容的打印
[root@docker-node1 test]# docker compose -f jcl.yml config
添加-q表示没有问题就不打印
[root@docker-node1 test]# docker compose -f jcl.yml config -q
8.3 Docker Compose 的yml文件
8.3.1设置端口
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: nginx
container_name: web
expose:
- 1234
[root@docker-node1 test]# docker compose up -d
[+] Running 2/2
✔ Network test_default Created 0.1s
✔ Container web Started 0.3s
[root@docker-node1 test]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f826496c749 nginx "/docker-entrypoint.…" 9 seconds ago Up 9 seconds 80/tcp, 1234/tcp web
8.3.2设置映射端口
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: nginx
container_name: web
ports:
- "80:80"
[root@docker-node1 test]# docker compose up -d
[+] Running 1/1
✔ Container web Started 0.4s
[root@docker-node1 test]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
40d0ce48632f nginx "/docker-entrypoint.…" 18 seconds ago Up 17 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp web
8.3.3给某些需要环境变量的镜像设置环境变量
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: jcl
[root@docker-node1 test]# docker compose up -d
[+] Running 2/2
✔ Container web Recreated 0.1s
✔ Container test-test1-1 Started
[root@docker-node1 test]# docker inspect test-test1-1
8.3.4 设置数据卷
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: jcl
volumes:
- /opt/mysql_data:/var/lib/mysql
[root@docker-node1 test]# docker compose down
[+] Running 2/1
✔ Container test-test1-1 Removed 1.2s
✔ Network test_default Removed 0.1s
[root@docker-node1 test]# docker compose up -d
[+] Running 2/2
✔ Network test_default Created 0.1s
✔ Container test-test1-1 Started 0.3s
三种方式查看
[root@docker-node1 test]# df
[root@docker-node1 ~]# docker inspect test-test1-1
[root@docker-node1 ~]# cd /opt/mysql_data/
[root@docker-node1 mysql_data]# ls
8.3.5网络
如果不设定的话会自动设定默认网络
[root@docker-node1 test]# docker compose up -d
[+] Running 2/2
✔ Network test_default Created 0.1s
✔ Container test-test1-1 Started 0.3s
[root@docker-node1 test]# docker network ls
NETWORK ID NAME DRIVER SCOPE
82ce7d690915 bridge bridge local
9dd9ecaef455 host host local
85484fc21f25 none null local
0683d3b78932 test_default bridge local
设定原生网络
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: busybox:latest
container_name: jcl
command: ["/bin/sh","-c","sleep 100000"]
network_mode: bridge
[root@docker-node1 test]# docker compose up -d
[+] Running 1/1
✔ Container jcl Started 0.3s
[root@docker-node1 test]# docker network ls
NETWORK ID NAME DRIVER SCOPE
82ce7d690915 bridge bridge local
创建网络设定
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: busybox:latest
container_name: jcl
command: ["/bin/sh","-c","sleep 100000"]
networks:
- mynet1
- mynet2
networks:
mynet1:
driver: bridge
mynet2:
driver: macvlan
[root@docker-node1 test]# docker compose up -d
[root@docker-node1 test]# docker exec -it jcl sh
8.3.6指定ip和网关
[root@docker-node1 test]# vim docker-compose.yml
services:
test1:
image: busybox:latest
container_name: jcl
command: ["/bin/sh","-c","sleep 100000"]
networks:
- mynet1
- mynet2
test2:
image: busybox:latest
container_name: jcl1
command: ["/bin/sh","-c","sleep 100000"]
network_mode: default
networks:
default:
external: true
name: bridge
mynet1:
driver: bridge
mynet2:
ipam:
driver: default
config:
- subnet: 172.26.0.0/24
gateway: 172.26.0.254
[root@docker-node1 test]# docker compose up -d
[+] Running 2/2
✔ Container jcl1 Started 0.4s
✔ Container jcl Running
[root@docker-node1 test]# docker network ls
NETWORK ID NAME DRIVER SCOPE
82ce7d690915 bridge bridge local
9dd9ecaef455 host host local
85484fc21f25 none null local
0683d3b78932 test_default bridge local
d2c2a93a7039 test_mynet1 bridge local
a5f129c20888 test_mynet2 macvlan local
[root@docker-node1 test]# docker exec -it jcl sh
8.3.7 设定数据卷
[root@docker-node1 test]# vim docker-compose.yml
[root@docker-node1 test]# docker compose up -d
[+] Running 3/3
✔ Volume "jcl" Created 0.0s
✔ Container jcl1 Started 10.4s
✔ Container jcl Running
[root@docker-node1 test]# docker exec -it jcl1 sh
将容器停掉也可以看到数据卷在本地上
[root@docker-node1 test]# docker compose down
8.4 企业示例
利用容器编排完成haproxy和nginx负载均衡架构实施
先安装一个haproxy在宿主机上,编辑配置文件
[root@docker-node1 _data]# dnf install haproxy -y --downloadonly --downloaddir=/mnt
[root@docker-node1 mnt]# rpm2cpio haproxy-2.4.17-3.el9.x86_64.rpm | cpio -id
将配置文件复制复制到doker目录下
[root@docker-node1 haproxy]# cp haproxy.cfg /var/lib/docker/volumes/conf/
加载镜像在doker仓库中
[root@docker-node1 ~]# docker load -i haproxy-2.3.tar.gz
43b3c4e3001c: Loading layer 83.87MB/83.87MB
87b571ab9f2c: Loading layer 48.64kB/48.64kB
85ff335ffae2: Loading layer 18.99MB/18.99MB
432ae7833e27: Loading layer 3.584kB/3.584kB
95d92b3c450e: Loading layer 1.536kB/1.536kB
Loaded image: haproxy:2.3
查看haproxy镜像的配置文件目录
[root@docker-node1 ~]# docker inspect haproxy:2.3
[root@docker-node1 haproxy]# vim /root/test/haproxy.yml
services:
web1:
image: nginx:latest
container_name: webserver1
restart: always
expose:
- 80
volumes:
- data_web1:/usr/share/nginx/html
networks:
- internel
web2:
image: nginx:latest
container_name: webserver2
restart: always
expose:
- 80
volumes:
- data_web2:/usr/share/nginx/html ——表示将宿主机上的nginx发布文件挂载到容器中的目录下
networks:
- internel
haproxy:
image: haproxy:2.3
restart: always
container_name: haproxy
ports:
- "80:80"
volumes:
- /var/lib/docker/volumes/conf/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg——挂载haproxy的配置文件
networks:
- internel
- extrnal
networks:
internel:
driver: bridge
extrnal:
driver: bridge
volumes:
data_web1:
name: data_web1
data_web2:
name: data_web2
开启服务
[root@docker-node1 test]# docker compose -f haproxy.yml up -d
查看容器是否运行起来
[root@docker-node1 test]# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
haproxy haproxy:2.3 "docker-entrypoint.s…" haproxy 11 seconds ago Up 10 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp
webserver1 nginx:latest "/docker-entrypoint.…" web1 11 seconds ago Up 10 seconds 80/tcp
webserver2 nginx:latest "/docker-entrypoint.…" web2 11 seconds ago Up 10 seconds 80/tcp
添加内容在nginx默认的发布文件中
[root@docker-node1 test]# echo webserver1 > /var/lib/docker/volumes/data_web1/_data/index.html
[root@docker-node1 test]# echo webserver2 > /var/lib/docker/volumes/data_web2/_data/index.html
测试访问172.25.254.100