admin管理员组文章数量:1516870
Docker
1、相关知识
(1)云计算
IAAS 基础架构即服务
PAAS 平台即服务
SAAS 软件即服务
(2)虚拟化技术
虚拟机 KVM ESXi
虚拟机管理系统 openstack vCenter
阿里云-->飞天管理系统
其他云厂商-->openstack
(3)容器化
容器编排工具:
Docker containerd rkt cri-o podman
镜像=app+依赖
app运行起来=镜像 变成 容器
容器化技术--->内核虚拟化
(4)内核技术
-namespace 命名空间 容器间隔离作用
-cgroup 控制组 容器资源限制
-UFS 容器文件系统 容器存储
(5)docker的三要素
image 镜像 静态文件
container 容器 动态、运行应用
repo 镜像文件
2、安装部署
(1)yum源安装
#查看软件的版本[root@docker ~]# dnf list docker-ce --showduplicates | sort -r
Last metadata expiration check: 1:56:41 ago on Sun 08 Feb 2026 04:50:44 PM CST.
Installed Packages
docker-ce.x86_64 3:29.2.1-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.2.1-1.el9 @docker-ce-stable
docker-ce.x86_64 3:29.2.0-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.1.5-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.1.4-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.1.3-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.1.2-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.1.1-1.el9 docker-ce-stable
#安装docker及依赖包[root@VM-0-8-rockylinux ~]# dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin[root@docker01 ~]# dnf -y install docker-ce-27.5.1 #安装指定版本'解释'
docker-ce #社区版
docker-ee #企业版
docker-ce-cli #docker的客户端
containerd.io #server的依赖
docker-buildx-plugin #插件
docker-compose-plugin #插件#设置镜像加速[root@docker ~]# vi /etc/docker/daemon.json{"registry-mirrors":[""]}[root@docker ~]# systemctl enable docker --now(2)离线安装
#可以上网的主机上下载rpm包[root@docker ~]# dnf download --resolve docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin[root@docker ~]# mkdir docker[root@docker ~]# mv * docker[root@docker ~]# tar -zcf docker.tar.gz docker/#未链接网络的机器,传输tar包,解压[root@docker ~]# dnf localinstall *.rpm3、docker命令
(1)查看命令
#查看版本信息[root@docker ~]# docker version
Client: Docker Engine - Community
Version: 29.2.1
API version: 1.53
Go version: go1.25.6
Git commit: a5c7197
Built: Mon Feb 217:21:30 2026
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 29.2.1
API version: 1.53(minimum version 1.44)
Go version: go1.25.6
Git commit: 6bc6209
Built: Mon Feb 217:17:39 2026
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v2.2.1
GitCommit: dea7da592f5d1d2b7755e3a161be07f43fad8f75
runc:
Version: 1.3.4
GitCommit: v1.3.4-0-gd6d73eb8
docker-init:
Version: 0.19.0
GitCommit: de40ad0
#查看本地镜像[root@docker ~]# docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
#查看本地运行的容器[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAME
#查看运行过的所有容器(包括已关闭)[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39f27f568b01 registry.cn-beijing.aliyuncs.com/xxhf/busybox:latest "sh"18 seconds ago Exited (0)13 seconds ago sweet_vaughan
c49af66566cb registry.cn-beijing.aliyuncs.com/xxhf/ubuntu:22.04 "/bin/bash"6 minutes ago Exited (0)29 seconds ago jovial_noyce
1cd861be81c4 registry.cn-beijing.aliyuncs.com/xxhf/hello-world:latest "/hello"8 minutes ago Exited (0)8 minutes ago epic_margulis
(2)镜像管理命令
| 镜像管理命令 | 说明 |
|---|---|
| docker images | 查看本机镜像 |
| docker pull 镜像名称:标签 | 下载镜像 |
| docker save 镜像名称:标签 -o 文件名 | 备份镜像为tar包 |
| docker load -i 备份文件名称 | 导入备份的镜像文件 |
| docker history 镜像名称:标签 | 查看镜像的制作历史 |
#从网络仓库拉取镜像[root@docker ~]# docker run registry.cn-beijing.aliyuncs.com/xxhf/hello-world:latest#拉取镜像并前台运行[root@docker ~]# docker run -it registry.cn-beijing.aliyuncs.com/xxhf/ubuntu:22.04
Unable to find image 'registry.cn-beijing.aliyuncs.com/xxhf/ubuntu:22.04' locally
22.04: Pulling from xxhf/ubuntu
43f89b94cd7d: Pull complete
Digest: sha256:f29870aec43cb049f711f7b807626214c9a97e428f93dfcdf3ba23d2c51c2fa5
Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/xxhf/ubuntu:22.04
root@c49af66566cb:/# cat /etc/os-release PRETTY_NAME="Ubuntu 22.04.3 LTS"NAME="Ubuntu"VERSION_ID="22.04"#解释
registry.cn-beijing.aliyuncs.com '仓库域名或者IP地址'
/
xxhf '用户名'
/
ubuntu:22.04 '仓库名:版本'-i 交互式
-t 终端
docker默认使用的网络仓库为hub.docker.com官方仓库,但目前国内无法去拉取数据
#基础镜像:
registry.cn-beijing.aliyuncs.com/xxhf/hello-world:latest
registry.cn-beijing.aliyuncs.com/xxhf/busybox:latest
registry.cn-beijing.aliyuncs.com/xxhf/centos:7
registry.cn-beijing.aliyuncs.com/xxhf/ubuntu:22.04
registry.cn-beijing.aliyuncs.com/xxhf/alpine:3.18
registry.cn-beijing.aliyuncs.com/xxhf/nginx:1.22.1
registry.cn-beijing.aliyuncs.com/xxhf/node-exporter:1.6.1
registry.cn-beijing.aliyuncs.com/xxhf/centos7-alirepo:7
registry.cn-beijing.aliyuncs.com/xxhf/redis:alpine
registry.cn-beijing.aliyuncs.com/xxhf/nginx:1.27.0-debian-12-r5
registry.cn-beijing.aliyuncs.com/xxhf/nginx:1.22.1-centos7
#Rockylinux:9
ccr.ccs.tencentyun.com/chijinjing/rockylinux:9
registry.cn-beijing.aliyuncs.com/xxhf/rockylinux:9
#Registry
registry.cn-beijing.aliyuncs.com/xxhf/registry:2
#Python:
registry.cn-beijing.aliyuncs.com/xxhf/python:3.7-alpine
#Go
registry.cn-beijing.aliyuncs.com/xxhf/golang:1.20
#stress:
registry.cn-beijing.aliyuncs.com/xxhf/stress:latest
#优雅退出:
registry.cn-beijing.aliyuncs.com/xxhf/iojs:onbuild
#wordpress:
registry.cn-beijing.aliyuncs.com/xxhf/mysql:5.7
registry.cn-beijing.aliyuncs.com/xxhf/mysql:8.0
registry.cn-beijing.aliyuncs.com/xxhf/wordpress:php7.4
---
#nginx-hello:
registry.cn-beijing.aliyuncs.com/xxhf/nginx-hello:plain-text
#http-app:
registry.cn-beijing.aliyuncs.com/xxhf/http-app:v1
registry.cn-beijing.aliyuncs.com/xxhf/http-app:v2
registry.cn-beijing.aliyuncs.com/xxhf/http-app:v3
registry.cn-beijing.aliyuncs.com/xxhf/http-app:v4
registry.cn-beijing.aliyuncs.com/xxhf/http-app:v5
#Ingress-Controller:
registry.cn-beijing.aliyuncs.com/xxhf/controller:v1.6.4
registry.cn-beijing.aliyuncs.com/xxhf/kube-webhook-certgen:v20220916
#metrics-server
registry.cn-beijing.aliyuncs.com/xxhf/metrics-server:v0.6.4
#蓝绿发布
registry.cn-beijing.aliyuncs.com/xxhf/rollouts-demo:green
registry.cn-beijing.aliyuncs.com/xxhf/rollouts-demo:blue
#Dashboard
registry.cn-beijing.aliyuncs.com/xxhf/dashboard:v2.7.0
#NFS-Provisioner:
registry.cn-beijing.aliyuncs.com/xxhf/nfs-subdir-external-provisioner:v4.0.2
#example-app
registry.cn-beijing.aliyuncs.com/xxhf/instrumented_app:latest
#Jenkins
ccr.ccs.tencentyun.com/chijinjing/jenkins:2.492.2-jdk17
#Gitlab
ccr.ccs.tencentyun.com/chijinjing/gitlab-ce:17.9.3-ce.0
#镜像改名[root@docker ~]# docker tag registry.cn-beijing.aliyuncs.com/xxhf/busybox:latest busybox:latest[root@docker ~]# docker images
i Info → U In Use
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
busybox:latest 023917ec6a88 6.61MB 2.22MB U
#删除镜像[root@docker ~]# docker rmi registry.cn-beijing.aliyuncs.com/xxhf/nginx:1.22.1
Untagged: registry.cn-beijing.aliyuncs.com/xxhf/nginx:1.22.1
#后台运行[root@docker ~]# docker run -d nginx:1.22.1
22e5678248a0850104dc925e1b60b824864934909bf9b9ea96ff359432c9ea38
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
22e5678248a0 nginx:1.22.1 "/docker-entrypoint.…"35 seconds ago Up 34 seconds 80/tcp charming_robinson
#运行并设置容器名称[root@docker ~]# docker run -d --name=nginx-02 nginx:1.22.1(3)容器管理命令
| 容器管理命令 | 说明 |
|---|---|
| docker run -it(d) 镜像名称:标签 | 创建容器 |
| docker ps | 查看容器的信息 |
| docker inspect 镜像名称|容器名称 | 查询(容器/镜像)的详细信息 |
| docker [start|stop|restart] 容器id | 启动、停止、重启容器 |
| docker exec -it 容器ID 启动命令 | 在容器内执行命令 |
| docker cp 路径1 路径2 | 拷贝文件:路径格式(本机路径 容器ID : /路径) |
#停止正在运行的容器docker stop 容器ID/NAME[root@docker ~]# docker stop 22e5678248a0 #启动已停止的容器[root@docker ~]# docker start 22e5678248a0 #查看所有的容器[root@docker ~]# docker ps -a#查看所有的容器ID[root@docker ~]# docker ps -aq#删除容器[root@docker ~]# docker rm 1cd861be81c4#查看容器ip地址'方法一
[root@docker ~]# docker run -it rockylinux:9 bash
[root@e33130461a1c /]# dnf provides ip
iproute-6.14.0-2.el9.x86_64 : Advanced IP routing and network device configuration tools
Repo : baseos
Matched from:
Provide : /sbin/ip
Filename : /usr/sbin/ip
[root@e33130461a1c /]# dnf -y install iproute
[root@e33130461a1c /]# ip a
2: eth0@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether fa:91:22:eb:d8:3a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.4/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
'方法二
[root@docker ~]# docker ps -a #查看要查看ip容器的ID或者NAME[root@docker ~]# docker exec -it 22e5678248a0 bash #前台运行
root@22e5678248a0:/# cat /etc/os-release #查看操作系统PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
root@22e5678248a0:/# apt update #更新apt仓库
root@22e5678248a0:/# apt install iproute2 #安装软件包
root@22e5678248a0:/# ip a #查看ip地址
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth
'方法三
[root@docker ~]# docker inspect 880142f26977 #查看容器的详细信息
[root@docker ~]# docker inspect -f '{{ .NetworkSettings.Networks.bridge.IPAddress }}' 880142f26977
[root@docker ~]# docker inspect 880142f26977 | grep IPAdd"IPAddress":"172.17.0.3",
#查看容器资源[root@docker ~]# docker stats 880142f26977
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
880142f26977 nginx-02 0.00% 2.594MiB / 1.917GiB 0.13% 1.62kB / 126B 0B / 487kB 3#临时启动容器,退出自动删除[root@docker ~]# docker run -it --rm busybox:latest sh#容器开机自动运行[root@docker ~]# docker run --restart always --name nginx-03 -d nginx:1.22.1#修改容器主机名[root@docker ~]# docker run -h busy -it busybox:latest sh
/ # hostname
busy
#容器添加域名解析hosts[root@docker ~]# docker run --add-host www.xxhf.cc:10.0.0.1 -it busybox:latest sh
/ # cat /etc/hosts10.0.0.1 www.xxhf.cc
#容器修改dns[root@docker ~]# docker run --dns 223.5.5.5 -it busybox sh
/ # cat /etc/resolv.conf
nameserver 223.5.5.5
#输出镜像到文件[root@docker ~]# docker save rockylinux:9 -o rockylinux9.tar.gz[root@docker ~]# lsdocker docker.tar.gz rockylinux9.tar.gz
#导入文件到镜像仓库[root@docker ~]# docker load -i rockylinux9.tar.g# 删除所有容器docker compose down
# 创建和启动容器docker compose up -d(4)数据库启动
# 1. 先创建自定义网络(可选,多容器通信用)docker network create mariadb-network
# 2. 启动 MariaDB 容器docker run -d\--name mariadb \--restart=unless-stopped \--network mariadb-network \-p3306:3306 \# 核心密码/数据库/用户配置-eMARIADB_ROOT_PASSWORD=Root@123456 \-eMARIADB_DATABASE=app_db \-eMARIADB_USER=app_user \-eMARIADB_PASSWORD=App@123456 \# 字符集配置-eMARIADB_CHARACTER_SET_SERVER=utf8mb4 \-eMARIADB_COLLATION_SERVER=utf8mb4_unicode_ci \# 允许 root 远程登录(生产环境建议限制IP,不要用%)-eMARIADB_ROOT_HOST=% \# 数据持久化(用 Docker 卷)-v mariadb-data:/var/lib/mysql \# 挂载自定义配置文件(可选,如需修改 my.cnf)-v /etc/mariadb/my.cnf:/etc/mysql/my.cnf \# 指定版本(避免 latest 自动更新)
mariadb:10.11
4、docker镜像仓库
(1) SAAS 注册
注册后就可以使用 hub.docker.com 阿里云 腾讯云 华为云
登录 docker login
/root/.docker/config.json
私有镜像仓库 认证 docker login
公有镜像 hub.docker.com
#阿里云个人镜像仓库登录docker login --username=nick3049651777 crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com
#阿里云镜像仓库密码:
86tCAcf-kU3wA.F
#从Registry中拉取镜像docker pull crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com/linux2026/linux-os:[镜像版本号]#将镜像推送到Registrydocker login --username=nick3049651777 crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com
docker tag [ImageId] crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com/linux2026/linux-os:[镜像版本号]docker push crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com/linux2026/linux-os:[镜像版本号]#使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
registry.aliyuncs.com/acs/agent 0.7-dfb6816 37bb9c63c8b2 7 days ago 37.89 MB
$ docker tag 37bb9c63c8b2 crpi-p8tgo9vjwdmcemxl-vpc.cn-beijing.personal.cr.aliyuncs.com/acs/agent:0.7-dfb6816
使用 "docker push" 命令将该镜像推送至远程。
$ docker push crpi-p8tgo9vjwdmcemxl-vpc.cn-beijing.personal.cr.aliyuncs.com/acs/agent:0.7-dfb6816
[root@docker ~]# docker tag rockylinux:9 crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com/linux2026/rockylinux:9[root@docker ~]# docker push crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com/linux2026/rockylinux:9 (2)Registry 仓库
Docker 官方提供 , 适合小型项目中使用
CNCF 项目
端口映射 宿主机:容器中应用监听端口
[root@docker ~]# docker run registry.cn-beijing.aliyuncs.com/xxhf/registry:2
Unable to find image 'registry.cn-beijing.aliyuncs.com/xxhf/registry:2' locally
2: Pulling from xxhf/registry
6263fb9c821f: Pull complete
86c1d3af3872: Pull complete
a15309931e05: Pull complete
a37b1bf6a96f: Pull complete
[root@docker ~]# docker images
registry.cn-beijing.aliyuncs.com/xxhf/registry:2 f95a2d3d08da 37.2MB 10.1MB U
[root@docker ~]# docker run -d -p 5000:5000 --name registry registry:2 #启动容器
4a5d5583d21a8b2ac593c9f0d0e0ca2731fe3604d15000b2c34de0099e0dec7b
[root@docker ~]# docker tag nginx:1.22.1 127.0.0.1:5000/xxhf/nginx:1.22.1 #修改镜像名[root@docker ~]# docker push 127.0.0.1:5000/xxhf/nginx:1.22.1 #推送镜像到registry仓库
The push refers to repository [127.0.0.1:5000/xxhf/nginx]
2a9f38700bb5: Pushed
f1f26f570256: Pushed
fd03b214f774: Pushed
ef2fc869b944: Pushed
ac713a9ef2cc: Pushed
fd071922d543: Pushed
1.22.1: digest: sha256:9081064712674ffcff7b7bdf874c75bcb8e5fb933b65527026090dacda36ea8b size: 1570[root@docker ~]# curl localhost:5000/v2/_catalog #查看仓库{"repositories":["xxhf/nginx"]}(3)Harbor 仓库
#准备离线安装包,解压[root@docker ~]# ls
harbor-offline-installer-v2.14.1.tgz
[root@docker ~]# tar -xf harbor-offline-installer-v2.14.1.tgz -C /usr/local/[root@docker ~]# cd /usr/local/harbor/[root@docker harbor]# ls
common.sh harbor.v2.14.1.tar.gz harbor.yml.tmpl install.sh LICENSE prepare
通用脚本 离线镜像 配置文件模版 安装脚本 重新加载脚本
[root@docker harbor]# cp harbor.yml.tmpl harbor.yml[root@docker harbor]# vim harbor.yml
hostname: reg.xxhf.cc #修改域名
http:
port: 80#https: #将ssl证书相关注释# port: 443#certificate: /your/certificate/path#private_key: /your/private/key/path
harbor_admin_password: 86tCAcf-kU3wA.F #设置两个密码
database:
password: 86tCAcf-kU3wA.F
[root@docker harbor]# ./install.sh #开始安装[root@docker harbor]# docker ps #安装完成,全是healthy状态
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7b94f0db22d goharbor/nginx-photon:v2.14.1 "nginx -g 'daemon of…"38 seconds ago Up 37 seconds (healthy)0.0.0.0:80->8080/tcp, [::]:80->8080/tcp nginx
8ce6744e9e31 goharbor/harbor-jobservice:v2.14.1 "/harbor/entrypoint.…"38 seconds ago Up 32 seconds (healthy) harbor-jobservice
b970f626a6e1 goharbor/harbor-core:v2.14.1 "/harbor/entrypoint.…"38 seconds ago Up 37 seconds (healthy) harbor-core
4d6a59d2e4d5 goharbor/registry-photon:v2.14.1 "/home/harbor/entryp…"38 seconds ago Up 38 seconds (healthy) registry
48cdd5b6ac95 goharbor/harbor-db:v2.14.1 "/docker-entrypoint.…"38 seconds ago Up 38 seconds (healthy) harbor-db
6195cfd58974 goharbor/harbor-registryctl:v2.14.1 "/home/harbor/start.…"38 seconds ago Up 38 seconds (healthy) registryctl
c0ebd3b7b121 goharbor/redis-photon:v2.14.1 "redis-server /etc/r…"38 seconds ago Up 38 seconds (healthy) redis
64b287f5c2fb goharbor/harbor-portal:v2.14.1 "nginx -g 'daemon of…"38 seconds ago Up 38 seconds (healthy) harbor-portal
3413fce6aefa goharbor/harbor-log:v2.14.1 "/bin/sh -c /usr/loc…"39 seconds ago Up 38 seconds (healthy)127.0.0.1:1514->10514/tcp
#windows做域名解析,reg.xxhf.cc 解析到本机的IP地址,然后浏览器访问[root@docker harbor]# vim /etc/hosts #做域名解析82.157.173.129 reg.xxhf.cc
[root@docker harbor]# docker login reg.xxh.cc
Username: admin
Password:
Error response from daemon: Get "": dial tcp: lookup reg.xxh.cc on 183.60.83.19:53: no such host#默认使用的https登录的,需要设置不安全的仓库#添加 不安全的仓库 /etc/docker/daemon.json[root@docker harbor]# vim /etc/docker/daemon.json{"registry-mirrors":[""],
"insecure-registries":["reg.xxhf.cc"]#新增此行,设置不安全的仓库}[root@docker harbor]# systemctl restart docker[root@docker ~]# vim .docker/config.json #仓库登录信息{"auths":{"crpi-p8tgo9vjwdmcemxl.cn-beijing.personal.cr.aliyuncs.com":{"auth":"bmljazMwNDk2NTE3Nzc6ODZ0Q0FjZi1rVTN3QS5G"}}}[root@docker harbor]# docker compose restart #重启所有容器[root@docker harbor]# echo '86tCAcf-kU3wA.F' | docker login reg.xxhf.cc --username admin --password-stdin
WARNING! Your credentials are stored unencrypted in'/root/.docker/config.json'.
Configure a credential helper to remove this warning. See
Login Succeeded
[root@docker harbor]# docker tag alpine:3.18 reg.xxhf.cc/linux-os/alpine:3.18 #测试上传[root@docker harbor]# docker push reg.xxhf.cc/linux-os/alpine:3.18
The push refers to repository [reg.xxhf.cc/linux-os/alpine]
930bdd4d222e: Pushed
3.18: digest: sha256:b12c7d46bc14b4260b9e42714688e2dbf5dee973b291a4c12e0e1539404d9f1d size: 528#上传证书
添加证书
YAML
# https related config
https:
# https port for harbor, default is 443
port: 443# The path of cert and key files for nginx
certificate: /usr/local/harbor/certs/reg.xxhf.cc_bundle.crt
private_key: /usr/local/harbor/certs/reg.xxhf.cc.cloud.key
重启 harbor
YAML
docker-compose down -v#进入harbor的安装目录#修改配置文件
./prepare
# 重新启动docker compose up -d
常用功能
修改配置文件重新加载
YAML
docker compose down -v#进入harbor的安装目录#修改配置文件
./prepare
# 重新启动docker compose up -d5、镜像制作
(1)制作方法
1、docker commit
2、Dockerfile脚本
#查看制作过程[root@docker ~]# docker history rockylinux:9
IMAGE CREATED CREATED BY SIZE COMMENT
5cdcd7ab7142 2 years ago CMD ["/bin/bash"] 0B buildkit.dockerfile.v0
<missing>2 years ago ADD layer.tar.xz / # buildkit 195MB buildkit.dockerfile.v0#制作原理
bootfs GRUB,加载内核
rootfs 根文件系统
容器中只有rootfs,没有bootfs(共用宿主机)
不同linux发行版本的区别就是rootfs不同
docker中rootfs是只读模式,已有分层只读,最上层可写层
(2)docker commit制作
'运行已有的系统镜像'[root@docker ~]# docker run -it rockylinux:9 '进入容器部署需要的环境'[root@bd9ec148923d /]# dnf -y install wget vim telnet'退出容器,查看容器ID'[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bd9ec148923d rockylinux:9 "/bin/bash"2 minutes ago Exited (0)10 seconds ago
'制作新镜像'[root@docker ~]# docker commit bd9ec148923d rocky9:v1'查看制作的新镜像'[root@docker ~]# docker images
rocky9:v1 2605e39fb8a9 409MB 118MB
'运行新镜像,验证环境是否打包成功'[root@docker ~]# docker run -it rocky9:v1[root@864a921dd7dd /]# rpm -q wget telnet
wget-1.21.1-8.el9_4.x86_64
telnet-0.17-85.el9.x86_64
(3)Dockerfile脚本制作
| 指令 | 核心作用 | 使用场景 | 最佳实践 |
|---|---|---|---|
FROM
| 指定基础镜像 | 所有 Dockerfile 必选 | 优先使用官方镜像,指定具体标签(避免 latest) |
RUN
| 执行构建命令 | 安装依赖、编译代码、修改配置 |
用
&&
合并命令,清理缓存(如 yum clean)
|
COPY
/
ADD
| 复制文件到镜像 | 复制代码、配置文件、依赖包 | 优先用 COPY(更安全),ADD 仅用于解压 / URL |
WORKDIR
| 设置工作目录 | 统一后续指令的执行路径 | 使用绝对路径,避免多层 cd |
ENV
| 设置环境变量 | 配置路径、版本、运行参数 | 合并多个 ENV 减少镜像层 |
CMD
/
ENTRYPOINT
| 容器启动命令 | 定义容器启动逻辑 | ENTRYPOINT 定核心逻辑,CMD 传默认参数 |
EXPOSE
| 声明暴露端口 | 文档说明容器端口用途 |
仅声明,运行时需用
-p
映射
|
VOLUME
| 定义数据卷 | 持久化容器数据(日志、数据库) |
避免容器内数据丢失,运行时可
-v
覆盖
|
USER
| 切换执行用户 | 提高容器安全性(避免 root 运行) | 先创建用户,再切换 |
ARG
| 构建参数 | 动态传递构建配置(如版本、环境) |
设置默认值,构建时用
--build-arg
传参
|
HEALTHCHECK
| 健康检查 | 监控容器运行状态 | 检查核心接口,设置合理的间隔 / 超时 |
ONBUILD
| 构建触发器 | 制作基础镜像(如开发环境) | 仅在基础镜像中使用,避免子镜像意外执行 |
'FROM
指定所创建镜像的 基础镜像。
任何 Dockerfile 中第一条指令必须为FROM指令。
格式:FROM <image>
#FROM rockylinux:9
'ARG
定义创建镜像过程中使用的变量。
格式为 ARG <name>[=<default value>]
在执行 docker build 时,可以通过 --build-arg [=] 来为变量赋值。当镜像编译成功后,ARG 指定的变量将不再存在(ENV 指定的变量将在镜像中保留)
#ARG VERSION=1.26.0'LABEL
LABEL 指令可以为生成的镜像添加元数据标签信息。
格式为LABEL <key>=<value> <key>=<value> <key>=<value> ...。
#LABEL version="1.0.0-rc3"
#LABEL author="chijinjing@xinxianghf.com" date="2023-01-01"
'EXPOSE
声明 镜像内服务监听的端口。
格式为 EXPOSE <port>[<port>/<protocol>...]#EXPOSE 80 443 'ENV
指定环境变量,在镜像生成过程中会被后续RUN指令使用,在镜像启动的容器中也会存在。
格式为 ENV <key>=<value> ...,修改的时候可以docker run -e key=value
#ENV APP_VERSION=1.0.0
#ENV APP_HOME=/usr/local/app
'ENTRYPOINT
指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数。
支持两种格式:
•ENTRYPOINT ["executable", "param1", "param2"]: 使用 exec 执行; 建议使用这种方式
•ENTRYPOINT command param1 param2: 在 shell 终端中执行。
每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个起效。
#ENTRYPOINT /usr/sbin/nginx#ENTRYPOINT ["/usr/sbin/nginx"]'VOLUME 卷
创建一个数据卷挂载点。
格式为 VOLUME ["/data"]
运行容器时可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要保持的数据。
"USER
指定运行容器时的用户名或 UID,后续的 RUN 等指令也会使用指定的用户身份。
格式为 USER daemon
当服务不需要管理员权限时,可以通过该命令指定运行用户,并且可以在Dockerfile中创建所需要的用户。
#USER redis
'WORKDIR
为 RUN、CMD、ENTRYPOINT 指令配置工作目录
格式为 WORKDIR /path/to/workdir
可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
#WORKDIR /a # mkdir /a && cd /a #WORKDIR b # mkdir b && cd b /a/b #WORKDIR c#RUN pwd # /a/b/c 'ONBUILD
指定当基于所生成镜像创建子镜像时,自动执行的操作指令。
格式为 ONBUILD [INSTRUCTION]
使用 docker build 命令创建子镜像 ChildImage 时(FROM ParentImage),会首先执行 ParentImage 中配置的 ONBUILD 指令:
#ONBUILD RUN yum -y install telnet wget && yum clean all
'STOPSIGNAL
指定容器接收退出的信号值:
#STOPSIGNAL signal'HEALTHCHECK
配置所启动容器如何进行健康检查(如何判断健康与否),自Docker 1.12开始支持。
有两种格式:
•HEALTHCHECK [OPTIONS] CMD command:根据所执行命令返回值是否为 0 来判断;
•HEALTHCHECK NONE:禁止基础镜像中的健康检查。
OPTION支持如下参数:
Dockerfile
--interval=DURATION (default: 30s):过多久检查一次;
--timeout=DURATION (default: 30s):每次检查等待结果的超时;
--retries=N (default: 3):如果失败了,重试几次才最终确定失败。
'SHELL
指定其他命令使用 shell 时的默认shell类型:
SHELL["executable", "parameters"]
默认值为["/bin/sh", "-c"]。
'RUN
运行指定命令。
格式为
•RUN <command> 在 shell 终端中执行
•RUN ["executable", "param1", "param2"] 使用 exec 执行
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像层。当命令较长时可以使用 \ 来换行。
#RUN dnf -y install telnet lrzsz iproute \
# && yum clean all
'CMD
CMD 指令用来指定启动容器时默认执行的命令。
支持三种格式:
•CMD ["executable", "param1", "param2"]:相当于执行 executable param1 param2,推荐方式;
•CMD command param1 param2:在默认的 Shell 中执行,提供给需要交互的应用;
•CMD ["param1", "param2"]:提供给 ENTRYPOINT 的默认参数。
每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。
如果用户启动容器时候手动指定了运行的命令(作为 run 命令的参数),则会覆盖掉 CMD 指定的命令。
比如 docker run -it centos:7 cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息
前两种格式的效果和 ENTRYPOINT 是一样的
'CMD 与 ENTRYPOINT 的区别
ENTRYPOINT 目的和 CMD 一样,都是指定容器启动程序及参数。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,实际执行时,将变为:<ENTRYPOINT> "<CMD>" 。
'ADD
添加内容到镜像。
格式为 ADD <src><dest>
该命令将复制指定的 <src> 路径下内容到容器中的 <dest> 路径下。
其中 <src> 可以是 Dockerfile 所在目录的一个相对路径(文件或目录);也可以是一个 URL;还可以是一个tar 文件(自动解压为目录)<dest> 可以是镜像内绝对路径,或者相对于工作目录(WORKDIR)的相对路径。
#ADD *.c /code/
'COPY
复制内容到镜像。
格式为 COPY <src><dest>
复制本地主机的 <src>(为Dockerfile所在目录的相对路径,文件或目录)下内容到镜像中的 <dest>。目标路径不存在时,会自动创建。
路径同样支持正则格式。
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是需要自动解压缩的场合。
# ==============================================================================
# 1. 基础镜像指令(必选,指定构建的基础镜像)
# ==============================================================================
# FROM:指定基础镜像,格式为 <镜像名>:<标签>,scratch 是空白镜像(无任何依赖)
# AS builder:为构建阶段命名,用于多阶段构建
FROM centos:7 AS builder
# ==============================================================================
# 2. 元数据指令(可选,添加镜像的描述信息)
# ==============================================================================
# LABEL:添加镜像元数据,键值对形式,可用于标注作者、版本、描述等
LABEL maintainer="your-name@example.com" # 维护者信息
LABEL version="1.0.0" # 镜像版本
LABEL description="Dockerfile 全指令示例,包含所有核心标签" # 镜像描述
LABEL org.opencontainers.image.licenses="MIT" # 开源协议(标准化标签)
# ==============================================================================
# 3. 构建阶段指令(修改镜像内部环境/文件)
# ==============================================================================
# ENV:设置环境变量,构建阶段和容器运行阶段都生效,可通过 ${KEY} 引用
ENV APP_HOME=/usr/local/app \
JAVA_HOME=/usr/local/jdk1.8 \
PATH=$PATH:$JAVA_HOME/bin
# ARG:构建参数,仅在构建阶段生效,构建时可通过 --build-arg 传递
# 可设置默认值,优先级:构建时传参 > ARG 默认值
ARG BUILD_ENV=prod # 构建环境:dev/test/prod
ARG APP_VERSION=1.0.0
# WORKDIR:设置工作目录(相当于 cd),后续指令都在该目录下执行,推荐使用绝对路径
# 若目录不存在,会自动创建,避免使用 RUN cd ... && 命令(易出错)
WORKDIR ${APP_HOME}
# ADD:复制文件/目录到镜像中,支持自动解压压缩包(tar.gz/tar.bz2 等)、支持URL
# 推荐优先使用 COPY(更安全,仅复制本地文件),ADD 仅在需要解压/URL 时使用
ADD ${JAVA_HOME}/
ADD app-${APP_VERSION}.tar.gz ${APP_HOME}/
# COPY:复制本地文件/目录到镜像中,仅支持本地文件,比 ADD 更推荐
# 格式:COPY <源路径> <目标路径>,源路径支持通配符
COPY ./src/ ${APP_HOME}/src/
COPY ./config/${BUILD_ENV}/application.yml ${APP_HOME}/config/
COPY ./start.sh ${APP_HOME}/
# RUN:执行构建阶段的命令,每一个 RUN 会创建一个镜像层,推荐用 && 合并命令减少层数
# 两种格式:shell 格式(默认)、exec 格式(推荐,避免 shell 解析问题)
# 1. shell 格式(适合简单命令)
RUN yum install -y gcc make wget && \
yum clean all && \ # 清理缓存,减小镜像体积
chmod +x ${APP_HOME}/start.sh
# 2. exec 格式(适合含特殊字符的命令,数组形式)
RUN ["mkdir", "-p", "${APP_HOME}/logs"]
# VOLUME:定义匿名卷,指定容器运行时的挂载目录,避免容器内数据丢失
# 运行时可通过 -v 覆盖,格式:VOLUME ["<路径1>", "<路径2>"]
VOLUME ["${APP_HOME}/logs", "/var/lib/mysql"]
# EXPOSE:声明容器暴露的端口(仅为文档说明,不会自动映射到宿主机)
# 格式:EXPOSE <端口1>/<协议> <端口2>/<协议>,默认 TCP
EXPOSE 8080/tcp 8090/udp
# USER:指定后续指令执行的用户/用户组(默认 root),提高容器安全性
# 先创建用户,再切换(避免权限不足)
RUN useradd -m appuser
USER appuser
# COPY --from:多阶段构建专用,从其他构建阶段复制文件(减少最终镜像体积)
# 从 builder 阶段复制编译好的文件到最终镜像
FROM centos:7 AS final
COPY --from=builder /usr/local/app /usr/local/app
COPY --from=builder /usr/local/jdk1.8 /usr/local/jdk1.8
# ENTRYPOINT:容器启动时执行的命令(不可被 docker run 命令行参数覆盖)
# 适合定义容器的核心启动逻辑,格式:shell 格式 / exec 格式(推荐)
# 若需传参,可配合 CMD 使用
ENTRYPOINT ["/usr/local/app/start.sh"]
# CMD:容器启动时的默认命令/参数,可被 docker run 命令行参数覆盖
# 三种格式:exec 格式(推荐)、shell 格式、参数格式(配合 ENTRYPOINT)
# 配合 ENTRYPOINT 时,CMD 作为默认参数
CMD ["--env", "prod", "--port", "8080"]
# ==============================================================================
# 4. 其他特殊指令(Docker 17.05+ 支持)
# ==============================================================================
# ONBUILD:触发器,当前镜像作为基础镜像时,子镜像构建时会执行该指令
# 适合制作基础镜像(如开发环境镜像)
ONBUILD COPY ./src/ ${APP_HOME}/src/
ONBUILD RUN ["mvn", "clean", "package", "-DskipTests"]
# HEALTHCHECK:容器健康检查,判断容器是否正常运行
# 格式:HEALTHCHECK [选项] CMD <命令>
# --interval:检查间隔(默认 30s),--timeout:超时时间(默认 30s),--retries:重试次数(默认 3)
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
CMD curl -f || exit 1
# SHELL:指定 shell 命令的默认解释器(默认 /bin/sh -c)
# 适合 Windows 或需要切换 bash 的场景
SHELL ["/bin/bash", "-c"]
(4)部署tomcat
#配置tomcat和java环境[root@docker01 tomcat]# ls
apache-tomcat-9.0.48.tar.gz jdk-11.0.11_linux-x64_bin.tar.gz mypress.war
Dockerfile mariadb-10.6.17.tar.gz mysql-connector-java-5.1.24.jar
[root@docker01 tomcat]# vim Dockerfile
FROM reg.xxhf.cc/os/rockylinux:9
LABEL author="sxtbao@126.com"date="2026-02-26"
EXPOSE 8080
ADD apache-tomcat-9.0.48.tar.gz /usr/local/
ADD jdk-11.0.11_linux-x64_bin.tar.gz /usr/local/
ENV JAVA_HOME=/usr/local/jdk-11.0.11
ENV PATH=$JAVA_HOME/bin:$PATH
WORKDIR /usr/local/apache-tomcat-9.0.48/
COPY mysql-connector-java-5.1.24.jar /usr/local/apache-tomcat-9.0.48/lib/
COPY mypress.war /usr/local/apache-tomcat-9.0.48/webapps
ENTRYPOINT ["/usr/local/apache-tomcat-9.0.48/bin/catalina.sh","run"][root@docker01 tomcat]# docker build -t mypress:v1 .[root@docker01 tomcat]# docker inspect abb334ed5795 | grep -i IPA#创建网络[root@docker01 tomcat]# docker network create net-mypress[root@docker01 tomcat]# docker run -d -p 8080:8080 --network net-mypress mypress:v2
'浏览器curl访问
#部署数据库# 启动 MariaDB 容器[root@docker01 tomcat]# docker run -d --name mariadb --restart=unless-stopped --network net-mypress -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=mypress -e MYSQL_USER=tom -e MYSQL_PASSWORD=123456 reg.xxhf.cc/app/mariadb:10.1 --default-authentication-plugin=mysql_native_password# 关键2:强制 10.1 用 mysql_native_password 插件(适配你的 5.1 驱动) (5)部署java应用
#官网下载java的jdk21[root@docker01 java-app]# ls
app.jar Dockerfile jdk-11.0.11_linux-x64_bin.tar.gz
[root@docker01 java-app]# vim Dockerfile
FROM rockylinux:9
LABEL authors="sxtbao@126.com"
ADD jdk-11.0.11_linux-x64_bin.tar.gz /usr/local/
ENV JAVA_HOME=/usr/local/jdk-11.0.11
ENV PATH=${JAVA_HOME}/bin:$PATH
EXPOSE 8080
WORKDIR /data/java
COPY app.jar /data/java
CMD ["java","-jar","app.jar"][root@docker01 java-app]# docker run -d java-app:v1[root@docker01 java-app]# docker inspect 91273a6cf912 | grep -i IPadd"SecondaryIPAddresses": null,
"IPAddress":"172.17.0.2",
"IPAddress":"172.17.0.2",
[root@docker01 java-app]# curl 172.17.0.2:8080
Hello, Spring Boot!6、构建上下文
•当运行 docker build 命令时,当前工作目录被称为构建上下文。build context
•docker build 默认查找当前目录的 Dockerfile 作为构建输入,也可以通过 -f 指定 Dockerfile。
○docker build -f ./Dockerfile
•当 docker build 运行时,首先会构建上下文传输给 docker daemon,把没用的文件包含在构建上下文时,会导致传输时间长,构建需要的资源多,构建出的镜像大等问题。
○试着找一下包含较多文件的目录试一下区别
○可以通过 .dockerignore 文件从编译上下文排除文件
•需要确认构建上下文清晰,建议创建一个单独的目录放置 Dockerfile,并在目录中运行 docker build。
在使用 Docker 构建镜像时,我们通常会将项目的文件和文件夹复制到镜像中。然而,并不是所有的文件和文件夹都需要被复制进镜像中,有时我们需要排除一些不需要的文件夹或文件。这就是使用 .dockerignore 文件的作用。
.dockerignore 文件类似于 .gitignore 文件,它用于告诉 Docker 哪些文件或文件夹不应该被复制到镜像中。当构建镜像时,Docker 引擎会根据 .dockerignore 文件的规则来排除指定的文件和文件夹。
如果我们只想将 app 文件夹复制到镜像中,而不包含 config 和 tests 文件夹,我们可以在 .dockerignore 文件中添加以下内容:
config/
tests/
.dockerignore 文件的规则
•#:用于添加注释。以 # 开头的行将被忽略。
•/path/to/folder:排除指定的文件夹以及其内容。
•/path/to/file:排除指定的文件。
•!:用于取反。如果文件夹被排除了,但是又想包含此文件夹内的某个文件,可以在前面加上 !。
# 忽略所有 .txt 文件
*.txt
# 忽略所有文件夹和子文件夹中的 .log 文件
**/*.log
# 排除 .git 文件夹及其内容
.git/
# 排除 node_modules 文件夹及其内容
node_modules/
# 但是包含 node_modules/myapp 文件夹及其内容!node_modules/myapp/
7、多平台/架构 镜像
# 查看本地镜像支持的架构 [root@docker java]# docker inspect java21 | grep -i arch"Architecture":"amd64",
# 查看远端仓库中的镜像支持的架构[root@docker ~]# docker manifest inspect nginx:1.22.1 | grep architecture"architecture":"amd64",
"architecture":"arm",
"architecture":"arm",
"architecture":"arm64",
"architecture":"386",
"architecture":"mips64le",
"architecture":"ppc64le",
"architecture":"s390x",
#制作多平台架构 镜像 # 确认 buildx 功能启用 [root@docker dockerfile-multi-platform]# docker buildx create --use quizzical_mahavira# 构建多平台镜像,并推送到仓库 docker buildx build --platform linux/amd64,linux/arm64 -t reg.xxhf.cc/library/rockylinux:v1 --push.
'docker buildx 构建的镜像只保存在缓存中,本地无法查看,如果需要在本地显示,需要加 --load 参数
8、Docker 网络
(1)docker网络的启动过程
Docker 服务启动时会首先在主机上自动创建一个 docker0 的虚拟网桥,网桥可以理解为一个软件交换机,负责挂载其上的接口之间进行包转发。
Docker 随机分配一个本地未占用的私有网段中的一个地址给 docker0 接口,默认是 172.17.0.0/16 网段。此后启动的容器会自动分配一个该网段的地址。
当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 互联接口。当向任一个接口发送包时,另外一个接口自动收到相同的包。互联接口的一端位于容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以veth 开头(例如 vethae12ch)。通过这种方式,主机可以与容器通信,容器之间也可以相互通信。如此一来,Docker 就创建了在主机和所有容器之间一个虚拟共享网络。
# 查看网桥# brctl 命令默认没有安装,安装命令: yum -y install bridge-utils[root@docker ~]# brctl show
bridge name bridge id STP enabled interfaces
br-108ad33126bc 8000.8ec65a1a5646 no
br-75dbfed6b88b 8000.52f4c601363a no veth11405ce
veth5ee5eb6
veth8c6f636
vetha41a4f2
vetha50cdf5
vethbad86e3
vethe2d28d7
vethe8d07b7
vethf2412d9
docker0 8000.ea9d29a9ee1a no
# 查看 docker0 接口[root@docker ~]# ifconfig docker0
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::e89d:29ff:fea9:ee1a prefixlen 64 scopeid 0x20<link>
ether ea:9d:29:a9:ee:1a txqueuelen 0(Ethernet)
RX packets 41199 bytes 2758916(2.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 57215 bytes 205087475(195.5 MiB)
TX errors 0 dropped 11 overruns 0 carrier 0 collisions 0(2)Docker 网络的连通性
1)容器访问外网
#容器访问外网
容器默认指定了网关为docker0网桥上的docker0内部接口。docker0内部接口同时也是宿主机的一个本地接口。因此,容器默认情况下可以访问到宿主机本地网络。如果容器要想通过宿主机访问到外部网络,则需要宿主机进行辅助转发。
1、开启内核包转发功能
在宿主机Linux系统中,检查转发是否打开,代码如下:
[root@docker ~]# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward =1
如果为0,说明没有开启转发,则需要手动打开:
[root@docker ~]# sysctl -w net.ipv4.ip_forward=12、配置 SNAT 规则
假设容器内部的网络地址为172.17.0.2,本地网络地址为10.0.2.2。容器要能访问外部网络,源地址不能为172.17.0.2,需要进行源地址映射(Source NAT,SNAT),修改为本地系统的IP地址10.0.2.2。
映射是通过iptables的源地址伪装操作实现的。查看主机nat表上 POSTROUTING 链的规则。该链负责数据包要离开主机前,改写其源地址:
Bash
[root@docker ~]# iptables -tnat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 7920 packets, 499K bytes)
pkts bytes target prot opt in out source destination
00 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
其中,上述规则将所有源地址在172.17.0.0/16网段,且不是从docker0接口发出的流量(即从容器中出来的流量),动态伪装为从系统网卡发出。
2)外部访问容器
#外部访问容器
容器允许外部访问,可以在docker [container] run 时候通过 -p 或 -P 参数来启用。
不管用哪种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则,将访问外部IP地址的包进行目标地址DNAT,将目标地址修改为容器的 IP 地址。
以一个开放 80 端口的 Nginx 容器为例,使用 -P 时,会自动映射本地 32768~65535 范围内的随机端口到容器的 80 端口:
[root@docker2 ~]# docker run --rm -P nginx:1.22.1[root@docker2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
23a196dbef09 nginx:1.22.1 "/docker-entrypoint.…"19 seconds ago Up 18 seconds 0.0.0.0:32768->80/tcp, [::]:32768->80/tcp vigilant_liskov
[root@docker2 ~]# iptables -t nat -nvL
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
00 DNAT 6 -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:32769 to:172.17.0.2:80
#使用-p 80:80 时,与上面类似,只是本地端口也为 80[root@docker ~]# docker run --rm -p 80:80 nginx:1.22.13)容器间互相访问
'方法一、--link
容器默认都连接在 Docker0 网桥上,都可以互相访问,相当连于在一台二层交换机上。
但是容器的 IP 地址是动态变化的,如果两个应用之间要互相访问就无法通过 IP 地址互相通信,Docker 提供两种方案来解决。
--link 传统方式,目前 Docker 官网已不建议使用
我们部署一个 Wordpress 应用
#下载镜像[root@docker2 ~]# docker pull registry.cn-beijing.aliyuncs.com/xxhf/wordpress:php7.4[root@docker2 ~]# docker tag registry.cn-beijing.aliyuncs.com/xxhf/wordpress:php7.4 wordpress:php7.4[root@docker2 ~]# docker pull registry.cn-beijing.aliyuncs.com/xxhf/mysql:8.0[root@docker2 ~]# docker tag registry.cn-beijing.aliyuncs.com/xxhf/mysql:8.0 mysql:8.0#运行数据库容器[root@docker2 ~]# docker run -d --name db_wordpress --restart always \-eMYSQL_ROOT_PASSWORD=wordpress \-eMYSQL_DATABASE=db_wordpress \-eMYSQL_USER=wordpress_rw \-eMYSQL_PASSWORD=123456\-p3306:3306 mysql:8.0
[root@docker2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
31fb03189486 mysql:8.0 "docker-entrypoint.s…"52 seconds ago Up 51 seconds 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp db_wordpress
#验证能否使用创建的用户连接成功[root@docker2 ~]# docker run -d --link db_wordpress -it mysql:8.0 -uroot -pwordpress -hdb_wordpress
174914d4206c5f3247b8ad36186ab2b660f62cbbfbbaa1ecd06754848e01cddb
#运行wordpress容器[root@docker2 ~]# docker run -d --name wordpress --restart always --link db_wordpress \-eWORDPRESS_DB_HOST=db_wordpress:3306 \-eWORDPRESS_DB_USER=wordpress_rw \-eWORDPRESS_DB_PASSWORD=123456\-eWORDPRESS_DB_NAME=db_wordpress \-p80:80 wordpress:php7.4
#如果安装有问题,可以查看日志[root@docker2 ~]# docker logs -f wordpress--link 是通过在本地解析文件 /etc/hosts 添加主机记录来实现DNS域名解析的。
[root@docker2 ~]# docker exec -it wordpress cat /etc/hosts127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 db_wordpress 31fb03189486
172.17.0.3 61c308af63d9
'方法二、自定义网络 User-defined networks'1、创建一个用户定义网络
[root@docker2 ~]# docker network create wp-net
d5777ade8160f5281ce08eed61e1ae4fa963578b6972bf0e5a487a4c85ef779d
[root@docker2 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
91b48473d846 bridge bridge local
2781d1354b5e hosthostlocal
a9cc685f6d2c none null local
d5777ade8160 wp-net bridge local2、重新运行 MySQL 和 WordPress 容器
[root@docker2 ~]# docker rm $(docker ps -aq) #清除之前创建的容器[root@docker2 ~]# docker run -d --name db_wordpress --restart always --network wp-net -e MYSQL_ROOT_PASSWORD=wordpress -e MYSQL_DATABASE=db_wordpress -e MYSQL_USER=wordpress_rw -e MYSQL_PASSWORD=123456 -p 3306:3306 mysql:8.0[root@docker2 ~]# docker run -d --name wordpress --restart always --network wp-net -e WORDPRESS_DB_HOST=db_wordpress:3306 -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_PASSWORD=wordpress -e WORDPRESS_DB_NAME=db_wordpress -p 80:80 wordpress:php7.4
自定义网桥可以自动实现容器间的DNS解析,没有修改 /etc/hosts 文件
(3)网络模式
1)host模式
Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable 规则等都与其他的 Network Namespace 隔离。一个 Docker 容器一般会分配一个独立的 Network Namespace。但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
例如,我们在10.10.101.105/24 的机器上用 host 模式启动一个含有web应用的Docker容器,监听 tcp80 端口。
当我们在容器中执行任何类似ifconfig命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用10.10.101.105:80即可,不用任何NAT转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。
# 运行一个使用 host 网络模式的容器[root@docker ~]# docker run -d --net=host nginx:1.22.1 # 容器详情中没有 IP 地址 [root@docker ~]# docker inspect 2fa38ed2c2f3 | grep -i ipaddr"SecondaryIPAddresses": null,
"IPAddress":"",
"IPAddress":"",
# 宿主机可以看到 80 端口的监听 [root@docker ~]# netstat -nltp | grep nginx
tcp 000.0.0.0:80 0.0.0.0:* LISTEN 4561/nginx: master
tcp6 00 :::80 :::* LISTEN 4561/nginx: master
# 宿主机可以访问 nginx 服务 [root@docker ~]# curl 127.0.0.12)bridge 模式
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口)。
Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。
# docker0 网桥 [root@docker ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024276df2867 no veth15882fe
# docker0 接口[root@docker ~]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
--network bridge:设置容器工作在bridge模式下,即将容器接口添加至 docker0 网桥。
#配置 docker0 网络配置:[root@docker2 ~]# vim /etc/docker/daemon.json #添加以下内容:{"bip":"192.168.1.1/24",
"fixed-cidr":"192.168.1.0/24"}1. bip: "192.168.1.1/24"
全称:bridge ip(网桥 IP);
作用:指定 Docker 默认网桥 docker0 的网关 IP 地址;
你的配置解读:把 docker0 网桥的 IP 设为 192.168.1.1,子网掩码为 /24(即 255.255.255.0);
默认值:Docker 默认会给 docker0 分配 172.17.0.1/16,你这里是手动改成了 192.168.1.1/24。
2. fixed-cidr: "192.168.1.0/24"
作用:限制 Docker 给默认网桥下的容器分配 IP 的网段范围;
你的配置解读:仅允许 Docker 从 192.168.1.0/24 网段中给容器分配 IP(容器 IP 会是 192.168.1.2 ~ 192.168.1.254);
关联 bip:fixed-cidr 必须是 bip 所在子网的子集(比如 bip 是 /24,fixed-cidr 不能是 /23 或 /16),否则 Docker 启动会报错。
[root@docker2 ~]# systemctl restart docker [root@docker2 ~]# ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255
3)none模式
此模式下容器不参与网络通信,运行于此类容器中的进程仅能访问本地环回接口,仅适用于进程无须网络通信的场景中,例如备份,进程诊断及各种离线任务等。
--network=none:设置模式容器工作在none模式下。
# 使用 none 网络模式,容器内只有 loopback 接口(4)docker网络底层实现
#启动一个运行在 none 模式的容器[root@docker2 ~]# docker run --network=none -it busybox[root@docker2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
14b31bc80f39 busybox "sh"37 seconds ago Up 37 seconds
[root@docker2 ~]# docker exec 14b31bc80f39 ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
#进入容器 namespace 查看网络配置[root@docker2 ~]# docker inspect 14b31bc80f39 | grep -i pid"Pid":401370,
"PidMode":"",
"PidsLimit": null,
[root@docker2 ~]# nsenter -t 401370 -n ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
#创建一个自定义网络 net0[root@docker2 ~]# docker network create -d bridge --subnet 172.100.0.0/16 net0#以上命令创建了一个名为 net0 的网桥,网络 ID 是 ce1dc69bf1f0ac9a05...。该网络使用的是 bridge 模式,网段是 172.100.0.0/16, 该网络的第一个IP地址 172.100.0.1 分配给了网桥,网桥的名字可以通过下面命令查到。 [root@docker2 ~]# ip a28: br-375cee259982: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether fa:9b:ac:fe:6c:48 brd ff:ff:ff:ff:ff:ff
inet 172.100.0.1/16 brd 172.100.255.255 scope global br-375cee259982
valid_lft forever preferred_lft forever
#如果想查看网桥的详细信息,可以使用 inspect 命令[root@docker2 ~]# docker network inspect net0#为容器绑定 veth pair 网卡,并接入上面创建的网桥上[root@docker2 ~]# PID=$(docker inspect -f '{{.State.Pid}}' 14b31bc80f39)[root@docker2 ~]# IPADDR=172.100.0.2[root@docker2 ~]# NETMASK=255.255.0.0[root@docker2 ~]# GATEWAY=172.100.0.1[root@docker2 ~]# ln -s /proc/$PID/ns/net /var/run/netns/$PID[root@docker2 ~]# ls /var/run/netns/401370[root@docker2 ~]# ip netns ls401370# 创建一对veth pair,vetha 和 vethb iplinkadd vetha type veth peer name vethb
# 将 vetha 绑定到网桥 br-ce1dc69bf1f0
brctl addif br-ce1dc69bf1f0 vetha
# 启用 vetha iplinkset vetha up
# vethb 放入指定的网络命名空间iplinkset vethb netns $PID# 在指定的命名空间中将 vethb 改名为 eth0 ip netns exec$PIDiplinkset dev vethb name eth0
# 启用 eth0 ip netns exec$PIDiplinkset eth0 up
# 为 eth0 添加 IP 地址ip netns exec$PIDip addr add$IPADDR/$NETMASK dev eth0
# 添加默认网关ip netns exec$PIDip route add default via $GATEWAY#验证容器内IP地址可以与外界正常通信[root@docker2 ~]# docker exec 14b31bc80f39 ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
29: eth0@if30: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
link/ether 4a:92:7d:05:b5:28 brd ff:ff:ff:ff:ff:ff
inet 172.100.0.2/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::4892:7dff:fe05:b528/64 scope link
valid_lft forever preferred_lft forever
[root@docker2 ~]# ping -c2 172.100.0.2
PING 172.100.0.2 (172.100.0.2)56(84) bytes of data.
64 bytes from 172.100.0.2: icmp_seq=1ttl=64time=0.113 ms
64 bytes from 172.100.0.2: icmp_seq=2ttl=64time=0.046 ms
9、Namespace
| 隔离类型 | 系统调用参数 | 内核版本 | 隔离了什么? |
|---|---|---|---|
| Mount (mnt) |
CLONE_NEWNS
| Linux 2.4.19 |
文件系统挂载点
。让容器拥有自己独立的文件系统目录树,执行
mount/umount
不会影响宿主机或其他容器 。
|
| UTS |
CLONE_NEWUTS
| Linux 2.6.19 |
主机名和域名
。让每个容器可以有自己的
hostname
,在网络上可以被视作一个独立的节点 。
|
| IPC |
CLONE_NEWIPC
| Linux 2.6.19 | 进程间通信资源 。隔离信号量、消息队列、共享内存等,防止容器间的进程通过 IPC 直接通信 。 |
| PID |
CLONE_NEWPID
| Linux 2.6.24 | 进程 ID 编号 。让容器内的进程拥有自己独立的 PID 编号空间,在容器内看到自己的 PID 是 1(如同 init 进程),而无法看到宿主机或其他容器的进程 。 |
| Network (net) |
CLONE_NEWNET
| Linux 2.6.29 | 网络资源 。隔离网络设备、IP 地址、路由表、端口、防火墙规则等。每个容器可以拥有自己独立的虚拟网卡和协议栈 。 |
| User |
CLONE_NEWUSER
| Linux 3.8 | 用户和用户组 ID 。实现用户权限的隔离,允许容器内的 root 用户映射为宿主机上的普通用户,从而提升安全性 。 |
| Cgroup |
CLONE_NEWCGROUP
| Linux 4.6 | Cgroup 根目录 。隔离控制组 (cgroup) 的视图,让容器以根的形式管理自己的 cgroup,更安全且便于资源限制 。 |
| Time |
CLONE_NEWTIME
| Linux 5.6 | 系统时间 。允许隔离系统启动时间(boot time)和单调时间(monotonic clock),使容器可以调整自己的时间视图 。 |
Docker 是借助 Linux 内核技术 Namespace 来实现隔离的, Linux Namespaces 机制提供一种资源隔离方案。 PID,IPC,Network 等系统资源不再是全局性的, 而是属于某个特定的 Namespace。 每个 namespace 下的资源对于其他 namespace 下的资源都是透明, 不可见的。 因此在操作系统层面上看, 就会出现多个相同 pid 的进程。 系统中可以同时存在多个进程号为 0,1,2 的进程, 由于属于不同的 namespace, 所以它们之间并不冲突。 而在用户层面上只能看到属于用户自己 namespace 下的资源, 例如使用 ps 命令只能列出自己 namespace 下的进程。这样每个 namespace 看上去就像一个单独的 Linux 系统。
#查看当前系统下的 namespace[root@docker2 ~]# lsns
NS TYPE NPROCS PID USER COMMAND
4026531834time1351 root /usr/lib/systemd/systemd showopts --switched-root --syste4026531835 cgroup 1281 root /usr/lib/systemd/systemd showopts --switched-root --syste4026531836 pid 1281 root /usr/lib/systemd/systemd showopts --switched-root --syste4026531837 user 1351 root /usr/lib/systemd/systemd showopts --switched-root --syste4026531838 uts 1251 root /usr/lib/systemd/systemd showopts --switched-root --syste#查看某个进程的 namespace[root@docker2 ~]# ls -la /proc/1/ns #查看pid为1的进程的命令空间
total 0
dr-x--x--x 2 root root 0 Feb 1310:03 .
dr-xr-xr-x 9 root root 0 Feb 1310:03 ..
lrwxrwxrwx 1 root root 0 Feb 1310:03 cgroup ->'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Feb 1314:47 ipc ->'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Feb 1310:03 mnt ->'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Feb 1310:03 net ->'net:[4026531840]'
lrwxrwxrwx 1 root root 0 Feb 1310:03 pid ->'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 1314:48 pid_for_children ->'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Feb 1314:47 time ->'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Feb 1314:48 time_for_children ->'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Feb 1314:47 user ->'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Feb 1314:47 uts ->'uts:[4026531838]'#进入某个 namespace # 进入系统主进程下执行 ip addr 命令,查看到的结果跟在系统 bash下 执行 ip addr 结果一样[root@docker ~]# nsenter -t 1 -n ip addr# 运行一个容器[root@docker ~]# docker run -d nginx:1.22.1
fba13bee996471204348f9545bc3b8f7ea790fcfe3a1d4cbecf9e37c2d69e0e3
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fba13bee9964 nginx:1.22.1 "/docker-entrypoint.…"5 seconds ago Up 4 seconds 80/tcp hardcore_pare
# 查看容器的进程号[root@docker ~]# docker inspect fba13bee9964 | grep -i pid "Pid":18810,
"PidMode":"",
"PidsLimit": null,
# 在容器的 namespace 中执行 命令[root@docker ~]# nsenter -t 18810 -n ip a #每个容器都在单独的 Namespace 里#Docker 默认没有启用 user namespace .#网络namespace
使用 ip netns 命令操作 network namespace
# 创建一个名为 nstest 的 network namespace[root@docker ~]# ip netns add nstest# 列出系统已存在的 network namespace [root@docker ~]# ip netns list
nstest
[root@docker ~]# ls -al /var/run/netns/
total 0
drwxr-xr-x 2 root root 60 Dec 8 08:35 .
drwxr-xr-x 31 root root 1140 Dec 8 08:35 ..
-r--r--r-- 1 root root 0 Dec 8 08:35 nstest
[root@docker ~]# # 在 network namespace 中执行命令[root@docker ~]# ip netns exec nstest ip addr1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# 删除 network namespace: nstest [root@docker ~]# ip netns delete nstest10、镜像的存储机制
(1)什么是 Docker 镜像
Docker镜像是一个只读的Docker容器模板,含有启动Docker容器所需的文件系统结构及其内容,因此是启动一个Docker容器的基础。Docker镜像的文件内容以及一些运行Docker容器的配置文件组成了Docker容器的静态文件系统运行环境——rootfs。可以这么理解,Docker镜像是Docker容器的静态视角,Docker容器是Docker镜像的运行状态。
rootfs是Docker容器在启动时内部进程可见的文件系统,即Docker容器的根目录。rootfs通常包含一个操作系统运行所需的文件系统,例如可能包含典型的类Unix操作系统中的目录系统,如 /dev、/proc、/bin、/etc、/lib、/usr、/tmp 及运行Docker容器所需的配置文件、工具等。
在传统的Linux操作系统内核启动时,首先挂载一个只读(read-only)的 rootfs,当系统检测其完整性之后,再将其切换为读写(read-write)模式。而在Docker架构中,当Docker daemon为Docker容器挂载rootfs时,沿用了Linux内核启动时的方法,即将rootfs设为只读模式。在挂载完毕之后,利用联合挂载(union mount)技术在已有的 只读rootfs上再挂载一个读写层 。这样,可读写层处于Docker容器文件系统的最顶层,其下可能联合挂载多个只读层,只有在Docker容器运行过程中文件系统发生变化时,才会把变化的文件内容写到可读写层,并隐藏只读层中的老版本文件。
(2) OverlayFS 存储原理
OverlayFS 结构分为三个层: LowerDir、Upperdir、MergedDir
LowerDir (只读层) 只读的 image layer,其实就是 rootfs, 在使用 Dockfile 构建镜像的时候, Image Layer 可以分很多层,所以对应的 lowerdir 会很多(源镜像)。
UpperDir (读写层) upperdir 则是在 lowerdir 之上的一层, 为读写层。容器在启动的时候会创建, 所有对容器的修改, 都是在这层。 比如容器启动写入的日志文件,或者是应用程序写入的临时文件。
MergedDir (合并层) merged 目录是容器的挂载点,在用户视角能够看到的所有文件,都是从这层展示的。
WorkDir (工作目录)
workdir 是 OverlayFS 内部用于处理写入操作的 临时目录 。
OverlayFS 演示:
# 创建需要的目录和文件
cd /tmp/
mkdir upper lower merged work
echo "I'm from lower" > lower/in_lower.txt
echo "I'm from upper" > upper/in_upper.txt
# `in_both` is in both directories
echo "I'm from lower" > lower/in_both.txt
echo "I'm from upper" > upper/in_both.txt
# 挂载 overlay 文件系统
mount -t overlay overlay \
-o lowerdir=/tmp/lower,upperdir=/tmp/upper,workdir=/tmp/work \
/tmp/merged
查看文件合并后的效果:
[root@docker tmp]# cd /tmp/merged/
[root@docker merged]# cat in_lower.txt
I'm from lower
[root@docker merged]# cat in_upper.txt
I'm from upper
# upper 层会覆盖 lower 层的文件
[root@docker merged]# cat in_both.txt
I'm from upper
创建一个新文件
[root@docker tmp]# echo 'new file' > merged/new_file
[root@docker tmp]# ls -l */new_file
-rw-r--r-- 1 root root 9 Oct 10 16:10 merged/new_file
-rw-r--r-- 1 root root 9 Oct 10 16:10 upper/new_file
删除一个文件
# 删除 merged 层的文件 in_both.txt
[root@docker tmp]# rm merged/in_both.txt
# 在 merged 层找不到文件 in_both.txt
[root@docker tmp]# ls -l merged/in_both.txt lower/in_both.txt upper/in_both.txt
ls: cannot access merged/in_both.txt: No such file or directory
-rw-r--r-- 1 root root 15 Oct 10 16:01 lower/in_both.txt
c--------- 1 root root 0, 0 Oct 10 16:12 upper/in_both.txt
# 在 upper 层还可以看到 in_both.txt, 但文件类型是 c (character )
[root@docker tmp]# ls -l upper/in_both.txt
c--------- 1 root root 0, 0 Oct 10 16:12 upper/in_both.txt
(3) 分析镜像存储结构
#下载一个 redis6 的镜像[root@docker ~]# docker pull redis:6#使用docker inspect命令查看镜像存储结构 [root@docker2 ~]# docker run -d redis:6[root@docker2 ~]# docker inspect bebc522ec9384"Storage":{"RootFS":{"Snapshot":{"Name":"overlayfs"}}}#Docker 镜像在磁盘上解压后的目录:[root@docker2 ~]# ll /var/lib/docker/
buildkit/ # 构建缓存
containers/ # 运行中容器的配置文件和相关数据
image/ # 镜像元数据
network/ # 网络配置
overlayfs/ # 可能在这里!(你的存储驱动对应的目录)
plugins/ # 插件
runtimes/ # 运行时配置
swarm/ # Swarm 集群数据
tmp/ # 临时文件
volumes/ # 数据卷(4)容器的存储机制
Docker 给容器提供的 数据持久化的方案 Volume
三种:
volumes #/var/lib/docker/volumes/ volume-name 挂载到容器中 bind mounts #宿主机的任意文件或目录 挂载到容器中
tmpfs #内存中创建一块空间 挂载到容器中1)volumes
[root@docker2 ~]# docker volume ls #查看卷
DRIVER VOLUME NAME
local 1b84e282a3c3a5505c843ca1401b8c38f7a48af07e4ec4ade085b0380874730c
local 2b777ff600aa0edfe3bb0f3c403cf396e4a120af06a593fce6cc46533f753b39
local 8df626f12edacee4dd7adb8f4fa27bc6dda067cc1fc8a3bc22418d5e5805f0f9
local 063a23ad5b502805956d054af8a559ed583d742c602baa749660f3411c17f0a8
local 848ca3cac391fb1aa351f5f8e77db370b1c6978b5465bb0208c32a9390a2ae94
local ce2db2ff48e95b473048665299273e23a92c945f3681b25f318025a04e70d9e8
local d85c1380eb9785deeee2967c0072ed6e52de2119a2999e5dde5194a72c9174b4
[root@docker2 ~]# ll /var/lib/docker/volumes/
total 60
drwx-----x 3 root root 4096 Feb 1314:28 063a23ad5b502805956d054af8a559ed583d742c602baa749660f3411c17f0a8
drwx-----x 3 root root 4096 Feb 1314:00 1b84e282a3c3a5505c843ca1401b8c38f7a48af07e4ec4ade085b0380874730c
drwx-----x 3 root root 4096 Feb 1314:27 2b777ff600aa0edfe3bb0f3c403cf396e4a120af06a593fce6cc46533f753b39
[root@docker2 ~]# docker volume create my-volume #创建卷[root@docker2 ~]# ll /var/lib/docker/volumes/
drwx-----x 3 root root 4096 Feb 1414:45 my-volume
[root@docker2 ~]# docker run -v my-volume:/data -d nginx:1.22.1
622bc0f863ac8d0ebc87a19055c7eec3f437d979e1483ae84b81db7aab5defb3
[root@docker2 ~]# docker exec -it 622bc0f863ac8 sh# touch abc.txt# ls
abc.txt boot dev docker-entrypoint.sh home lib64 mnt proc run srv tmp var
bin data docker-entrypoint.d etc lib media opt root sbin sys usr
# mv abc.txt data # ls data
abc.txt
[root@docker2 ~]# ll /var/lib/docker/volumes/my-volume/_data/
total 0
-rw-r--r-- 1 root root 0 Feb 1414:48 abc.txt
#容器删除,文件仍然存在[root@docker2 ~]# docker volume inspect my-volume #可以查看挂载信息及挂载点[{"CreatedAt":"2026-02-14T14:45:55+08:00",
"Driver":"local",
"Labels": null,
"Mountpoint":"/var/lib/docker/volumes/my-volume/_data",
"Name":"my-volume",
"Options": null,
"Scope":"local"}]#直接使用docker run -v 会自动创建卷[root@docker2 ~]# docker run -v db-volume:/var/lib/mysql -d mysql:latest
b1d01d0bc486d9579d9368cf94df7e66fb31ddca68f94f3b6304c3f2f8dea15a
[root@docker2 ~]# ll /var/lib/docker/volumes/
drwx-----x 3 root root 4096 Feb 1414:56 db-volume
2)bind mounts
#创建容器以任意目录为宿主机挂载目录[root@docker2 ~]# docker run -v /data/nginx:/data -d nginx:1.22.1[root@docker2 ~]# ls /data/nginx/[root@docker2 ~]# docker exec -it 6072be16c86f sh# touch /data/abc.txt# ls /data
abc.txt
[root@docker2 ~]# ls /data/nginx/ #同步成功
abc.txt
[root@docker2 ~]# docker logs -t 6072be16c86fa8b #查看容器日志[root@docker2 ~]# docker run -it -v /etc/passwd:/tmp/passwd:ro rockylinux:9 sh #可以添加ro属性,限制容器权限,不让修改宿主机的文件3)共享volume
#多个容器共同使用一组卷[root@docker2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6072be16c86f nginx:1.22.1 "/docker-entrypoint.…"10 minutes ago Up 10 minutes 80/tcp fervent_hermann
[root@docker2 ~]# docker run -d --volumes-from fervent_hermann nginx:1.22.1
30044d2ce23a974f93fcc17c54cffdb9bd3bb2a1de97d1ad86a9735df14200ac
[root@docker2 ~]# docker exec -it 30044d2ce23a974 sh# ls /data
abc.txt
4)删除volume
如果创建容器时从容器中挂载了volume,在 /var/lib/docker/volumes 下会生成与 volume 对应的目录,使用docker rm 删除容器并不会删除与 volume 对应的目录,这些目录会占据不必要的存储空间,即使可以手动删除,因为有些随机生成的目录名称是无意义的随机字符串,要查找它们与容器的对应关系也十分麻烦。所以在删除容器时需要对容器的 volume 妥善处理。
在删除容器时一并删除volume有以下两种方法。
•使用 dockerrm-v<container_name> 删除容器。
•在运行容器时使用 docker run --rm, --rm 标签会在容器停止运行时删除容器以及容器所挂载的volume。
注意:
1.在使用 docker volume rm 删除 volume 时,只有在没有任何容器使用时,该 volume 才能成功删除。
2.两种方法只会删除未命名的 volume,而对用户指定名字的 volume 进行保留。
3.如果 volume 是从宿主机中挂载的,无论对容器进行任何操作都不会导致其在宿主机中被删除。
[root@docker2 ~]# docker volume rm my-volume[root@docker2 ~]# docker rm -v d85c1380eb9785deeee2967c0072ed6e52de2119a2999e5dde5194a72c9174b4 #删除匿名卷11、镜像多阶段构建
#什么是多阶段构建
Docker多阶段构建是一种在单个Dockerfile中使用多个FROM语句的构建方式,旨在解决传统单阶段构建导致的镜像体积过大、构建过程复杂及潜在的安全风险等问题
#为什么要使用多阶段构建?
在Docker 17.05版本之前,为了编译像Go、Java或C++这样的应用,我们通常需要在构建镜像中包含完整的编译环境和源代码。这会导致最终的生产镜像体积臃肿(可能包含编译器、包管理器等不必要的文件),并且存在源码泄露的风险。
多阶段构建允许你将构建过程和运行环境彻底分离。你可以在第一个阶段(构建阶段)使用一个包含所有编译工具的大型基础镜像来编译应用,然后在第二个阶段(运行阶段)只将编译好的产物(如二进制文件)复制到一个极小的基础镜像(如alpine或scratch)中。这样,最终的镜像只包含运行应用所必需的内容,既安全又小巧。
#核心语法与结构
理解多阶段构建的关键在于掌握以下几个核心指令:
FROM ... AS <阶段名>:每个FROM指令标志着一个新阶段的开始。使用AS可以为该阶段命名,方便后续引用。
COPY --from=<阶段名或索引>:这是连接不同阶段的关键指令。它允许你将文件从指定的一个阶段(或一个完全外部的镜像)复制到当前阶段中。
最终镜像:默认情况下,Dockerfile中最后一个FROM阶段之后的指令会构成最终的镜像。你也可以在构建时通过--target参数指定要构建到哪个阶段为止
1、项目结构
text
my-go-app/
├── main.go
└── Dockerfile
2、应用程序代码 (main.go)
package main
import"fmt"
func main(){
fmt.Println("Hello, Docker Multi-stage Build!")}3、多阶段 Dockerfile
# 第一阶段:构建阶段# 使用包含完整Go工具链的镜像作为构建环境
FROM golang:1.21-alpine AS builder
# 设置工作目录
WORKDIR /app
# 将源代码复制到容器中
COPY main.go .# 编译应用。CGO_ENABLED=0 会生成一个静态链接的二进制文件,# 这样它就可以在没有任何外部依赖的scratch镜像中运行。
RUN CGO_ENABLED=0 go build -o myapp main.go
# 第二阶段:运行阶段# 使用一个空白的scratch镜像作为最终运行环境,它几乎没有任何额外的文件
FROM scratch AS runtime
# 从构建阶段(builder)将编译好的二进制文件复制过来
COPY --from=builder /app/myapp /myapp
# 指定容器启动时要执行的命令
CMD ["/myapp"]4、构建和运行
# 构建镜像docker build -t my-go-app:multi-stage .# 查看镜像大小docker images |grep my-go-app
# 运行容器docker run --rm my-go-app:multi-stage
通过这种方式,最终镜像仅包含编译好的myapp二进制文件,没有任何多余的shell、包管理器或编译器,极大地减小了镜像体积和攻击面。
12、cgroup限制容器资源
Cgroups是Linux内核的一个关键特性,它的全称是 Control Groups。简单来说,它允许你限制、记录和隔离一组进程的资源使用(如CPU、内存、磁盘I/O、网络等)。
在容器化技术(如Docker、LXC)和系统管理中,cgroups是实现资源控制和多租户隔离的基石。
#核心功能
cgroups主要提供以下四大功能:
1、资源限制 (Resource Limiting):你可以为一组进程设置资源使用的上限。例如,限制某个容器最多只能使用 1GB 内存 和 2个CPU核心。如果超过限制,可能会触发OOM(内存溢出) Killer或让进程等待。
2、优先级控制 (Prioritization):你可以控制一组进程可以获得多少CPU时间或I/O吞吐量。当资源紧张时,确保重要的进程比次要的进程获得更多资源。
3、统计与监控 (Accounting):cgroups会精确统计一组进程使用了多少资源。这是 docker stats 命令能够显示容器CPU、内存使用率的底层数据来源。
4、控制 (Control):你可以挂起、恢复或检查一组进程的状态。
cgroup 目前有两个版本,在Rockylinux 9 中使用的是 cgroup v2。
如何判断当前操作系统使用的是哪个版本,在操作系统中执行 mount | grep cgroup 命令:
[root@docker2 ~]# mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)(1)cgroup v1
#限制进程可使用的 CPU 资源
•在 cgroup cpu 子系统目录中创建目录结构
Bash
cd /sys/fs/cgroup/cpu
mkdir cpudemo
cd cpudemo
•运行 busyloop 程序
执行 top 查看 CPU 使用情况,CPU 占用 200%
•通过 cgroup 限制 cpu
Bash
cd /sys/fs/cgroup/cpu/cpudemo
•把进程添加到 cgroup 进程配置组
Bash
echops -ef|grep busyloop|grep-vgrep|awk'{print $2}'> cgroup.procs
•设置 cpuquota
Bash
echo10000> cpu.cfs_quota_us
•执行 top 查看 CPU 使用情况,CPU 占用变为 10%
(2)cgroup v2
限制进程可使用的 CPU 资源
1.创建一个目录 example ,这个目录表示一个进程组
cd /sys/fs/cgroup
mkdir example
2.在 example 进程组中会有各种配置文件
[root@docker example]# ls
cgroup.controllers cpu.weight.nice memory.low
cgroup.events hugetlb.1GB.current memory.max
cgroup.freeze hugetlb.1GB.events memory.min
cgroup.kill hugetlb.1GB.events.local memory.numa_stat
cgroup.max.depth hugetlb.1GB.max memory.oom.group
cgroup.max.descendants hugetlb.1GB.numa_stat memory.peak
3.限制进程 可以使用的 CPU 资源
先启动一个占用 CPU 的程序,找到程序的进程号
# ps -ef | grep busyloop
root 1556771158039916:14 pts/2 00:01:06 ./busyloop
# 把进程号添加到 cgroup.procs 文件 中# echo 155677 >> cgroup.procs# v2 版本限制cpu的文件使用 cpu.max, 这样会限制 155677 进程最多使用 1 核 CPU.# echo "100000 100000" > cpu.max (3)docker限制容器资源
#限制容器 CPU 资源
运行一个容器,限制可以使用2个cpu,使用 stress 占用4个cpu
[root@docker ~]# docker run -it --rm --cpus=2 registry.cn-beijing.aliyuncs.com/xxhf/stress /bin/bash
root@2e7bdd1296cb:/# stress -c 4
观察容器CPU利用率
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
2e7bdd1296cb nifty_banach 200.56% 788KiB / 15GiB 0.01% 656B / 0B 0B / 0B 6
容器 CPU 的负载为 200%,它的含义为单个 CPU 负载的两倍。我们也可以把它理解为有两颗 CPU 在 100% 的为它工作。
观察宿主机CPU利用率
top - 22:45:42 up 27 days, 13:25, 3 users, load average: 2.66, 1.03, 0.81
Tasks: 160 total, 5 running, 155 sleeping, 0 stopped, 0 zombie
%Cpu0 :0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 :49.8 us, 0.0 sy, 0.0 ni, 50.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 :0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 :50.5 us, 0.0 sy, 0.0 ni, 49.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu4 :0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu5 :49.5 us, 0.0 sy, 0.0 ni, 50.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu6 :50.3 us, 0.0 sy, 0.0 ni, 49.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu7 :0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem :15731672 total, 11204824 free, 511580 used, 4015268 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 14883056 avail Mem
四个 CPU 的负载都是 50%,加起来容器消耗的 CPU 总量就是两个 CPU 100% 的负载。
#Docker 限制容器内存资源 [root@docker ~]# docker run -it --rm -m 300M registry.cn-beijing.aliyuncs.com/xxhf/stress /bin/bash
root@c8bb524c6f28:/# stress --vm 1 --vm-bytes 300M
stress: info: [11] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [11](416)<-- worker 12 got signal 9
stress: WARN: [11](418) now reaping child worker processes
stress: FAIL: [11](452) failed run completed in 0s
root@c8bb524c6f28:/# stress --vm 1 --vm-bytes 290M
stress: info: [13] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
使用 docker status 命令 可以查看容器资源使用情况 :
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
c8bb524c6f28 objective_knuth 100.17% 282.9MiB / 300MiB 94.30% 656B / 0B 0B / 0B 3top - 23:16:02 up 27 days, 13:55, 3 users, load average: 1.00, 1.00, 1.25
Tasks: 157 total, 3 running, 154 sleeping, 0 stopped, 0 zombie
%Cpu(s): 2.3 us, 10.6 sy, 0.0 ni, 87.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem :15731672 total, 10995496 free, 709948 used, 4026228 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 14684688 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 22456 root 20030065220633648 R 100.01.34:42.47 stress
如果操作系统有swap 分区,容器会使用swap分区来保存内存文件,需要使用 'stress --vm 1 --vm-bytes 600M' 才会触发限制。
或者加一个参数 --memory-swap=300M,表示 可以使用的内存和交换空间的总和
docker run -it--rm-m 300M --memory-swap=300M registry.cn-beijing.aliyuncs.com/xxhf/stress /bin/bash
按照官方文档的理解,如果指定 -m 内存限制时不添加 --memory-swap 选项,则表示容器中程序可以使用 100M 内存和 100M swap 内存。默认情况下,--memory-swap 会被设置成 memory 的 2倍。
限制容器使用 256M 内存,交换分区可以使用 512M。
docker run -it--memory 256m --memory-swap 512m nginx:1.22.1
为容器保留 128M 内存空间,确保在资源有限的环境中,关键业务也可以正常运行。
docker run -it --memory-reservation 128m nginx:1.22.1
监控容器内存使用
docker stats --format"table {{.Name}}\t{{.MemUsage}}"#附录:
Java 内存溢出
# 初始堆大小(-Xms):设置 JVM 启动时的堆内存大小。# 最大堆大小(-Xmx):设置堆的最大内存限制。# -XX:+HeapDumpOnOutOfMemoryError:启用在发生 OOM 时自动生成堆转储。# -XX:HeapDumpPath:指定堆转储文件的保存路径(如果没有指定,默认路径是工作目录)。java-Xms2048m-Xmx2048m-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/tmp -jar appname.jar
13、YAML文件
#基本语法规则如下
•使用空白与缩进表示层次(有点类似 Python),可以不使用花括号和方括号。
•可以使用 # 书写注释,比起 JSON 是很大的改进。
•对象(字典)的格式与 JSON 基本相同,但 Key 不需要使用双引号。
•数组(列表)是使用 - 开头的清单形式(有点类似 MarkDown)。
•表示对象的 : 和表示数组的 - 后面都必须要有 空格。
•可以使用 --- 在一个文件里分隔多个 YAML 对象
#YAML 支持的 数据结构 有三种。
•对象:键值对的集合,又称为 映射(mapping)/ 哈希(hashes) / 字典(dictionary)
•数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
•纯量(scalars):单个的、不可再分的值
(1)数组
OS:- linux
- macOS
- Windows
(2)对象
Kubernetes:master:1worker:3(3)纯量
纯量是最基本的、不可再分的值。以下数据类型都属于 JavaScript 的纯量。
•字符串
•布尔值
•整数
•浮点数
•Null
•时间
•日期
#数值直接以字面量的形式表示。number:12.30#布尔值用true和false表示。isSet:true#时间采用 ISO8601 格式。iso8601:2001-12-14t21:59:43.10-05:00#字符串
字符串是最常见,也是最复杂的一种数据类型。
字符串默认不使用引号表示。
str: 这是一行字符串
如果字符串之中包含 空格或特殊字符,需要放在引号之中。
str:'内容: 字符串'
单引号和双引号都可以使用,双引号不会对特殊字符转义。
s1:'内容\n字符串's2:"内容\n字符串"
字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。
str: 这是一段
多行
字符串
多行字符串可以使用|保留换行符,也可以使用>折叠换行。
this:|
Foo
Barthat:>
Foo
Bar
+表示保留文字块末尾的换行,-表示删除字符串末尾的换行。
s1:|
Foo
Bar
Second s2:|+
Foo
s3:|-
Foo
14、docker compose
(1)简介
Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。
Compose 定位是 "定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications) "
我们通过一个 Dockerfile 文件可以让用户很方便的定义一个单独的应用容器镜像。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 或 compose.yml模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
Compose 中有两个重要的概念:
•服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
•项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
(2)安装
目前 Docker 官方用 GO 语言重写了 Docker Compose,并将其作为了 docker cli 的子命令,称为 Compose V2。你可以参照官方文档安装,然后将 v1 版的 docker-compose 命令替换为 docker compose,即可使用 Docker Compose。
#V1 版本的安装curl-L`uname-s`-`uname-m`> /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
#V2 版本的安装
yum install docker-compose-plugin
(3)顶级指令结构
version:'3.8'# Compose文件版本name: full-application # 项目名称x-defaults:&defaults# 自定义默认配置restart: always # 默认重启策略logging:# 默认日志配置driver: json-file
options:max-size:"10m"include:# 包含其他配置文件- ./shared-services.yml
services:# 服务定义app:# Web应用服务image: app:latest # 使用镜像networks:# 连接到网络- frontend
- backend
volumes:# 挂载卷- app-data:/data
db:# 数据库服务image: postgres:15# 使用PostgreSQL镜像networks:# 连接到网络- backend
volumes:# 挂载卷- db-data:/var/lib/postgresql/data
networks:# 网络定义frontend:# 前端网络driver: bridge # 桥接网络ipam:# IPAM配置config:-subnet: 172.20.0.0/24
backend:# 后端网络driver: bridge # 桥接网络internal:true# 内部网络volumes:# 数据卷定义app-data:# 应用数据卷driver: local # 本地驱动db-data:# 数据库数据卷driver: local # 本地驱动configs:# 配置定义app-config:# 应用配置file: ./config/app.yml # 配置文件路径secrets:# 密钥定义db-password:# 数据库密码file: ./secrets/db_pass.txt # 密钥文件路径(4)services (服务定义)
services:# 定义所有服务的顶级指令service-name:# 服务名称,可自定义# 镜像相关image: image-name:tag # 指定容器运行的镜像名称和标签build:# 从 Dockerfile 构建镜像的配置context: ./path # 构建上下文路径dockerfile: Dockerfile # 指定 Dockerfile 文件名args:# 构建时的参数- ARG_NAME=value # 传递给构建过程的参数target: build-stage # 多阶段构建中的目标阶段cache_from:# 构建缓存来源- image:tag # 用作缓存的镜像labels:# 为构建的镜像添加标签- key=value # 标签键值对network: host # 构建时的网络模式shm_size: 2gb # 构建时的共享内存大小extra_hosts:# 构建时的额外主机映射- hostname:ip # 主机名到IP的映射# 容器基础配置container_name: custom-container-name # 自定义容器名称,取代自动生成的名称hostname: custom-hostname # 容器内部的主机名restart: always # 容器退出时的重启策略stdin_open:true# 保持标准输入打开,相当于 docker run -itty:true# 分配伪终端,相当于 docker run -t# 网络配置ports:# 端口映射配置-"8080:80"# 宿主机8080端口映射到容器80端口-"8443:443"# 宿主机8443端口映射到容器443端口expose:# 暴露端口给链接的服务,但不映射到宿主机-"3306"# 暴露3306端口-"6379"# 暴露6379端口networks:# 连接到的网络- network-name # 连接到名为network-name的网络- another-network # 连接到多个网络dns:# 自定义DNS服务器- 8.8.8.8 # Google DNS- 114.114.114.114 # 国内DNSdns_search:# DNS搜索域- example.com # 当解析主机名时自动追加的域名- local # local域extra_hosts:# 在/etc/hosts中添加额外主机映射-"host.docker.internal:host-gateway"# Docker内部访问宿主机-"api.local:192.168.1.100"# 自定义主机名映射domainname: example.com # 容器的主机域名mac_address: 02:42:ac:11:65:43# 设置容器的MAC地址# 存储配置volumes:# 卷挂载配置- ./local:/container:ro # 挂载本地目录到容器,只读权限- data-volume:/var/lib/data # 挂载数据卷tmpfs:# 挂载tmpfs文件系统到容器- /tmp:size=100M # 在/tmp挂载大小100M的tmpfs- /var/cache # 在/var/cache挂载tmpfsworking_dir: /app # 容器启动后的工作目录volumes_from:# 从其他容器挂载卷- container-name:ro # 从指定容器挂载卷,只读模式# 环境变量environment:# 设置环境变量- ENV_VAR=value # 直接设置环境变量- DEBUG=true # 设置调试模式env_file:# 从文件加载环境变量- ./common.env # 公共环境变量文件- ./secrets.env # 密钥环境变量文件# 资源限制mem_limit: 512m # 内存使用上限mem_reservation: 256m # 内存软限制,保证最低内存cpus:'1.5'# CPU使用上限,1.5个核心ulimits:# 系统资源限制nproc:# 用户进程数限制soft:65535# 软限制hard:65535# 硬限制nofile:# 文件句柄数限制soft:20000# 软限制hard:40000# 硬限制# 依赖与启动depends_on:# 服务启动依赖- db # 依赖于db服务- redis # 依赖于redis服务healthcheck:# 健康检查配置test:["CMD","curl","-f",""]# 健康检查命令interval: 30s # 检查间隔时间timeout: 10s # 检查超时时间retries:3# 重试次数start_period: 40s # 启动后等待多久开始检查command: npm start # 覆盖容器默认命令entrypoint: /app/entrypoint.sh # 覆盖容器默认入口点# 日志与标签logging:# 日志配置driver: json-file # 日志驱动类型options:# 日志驱动选项max-size:"10m"# 单个日志文件最大大小max-file:"3"# 最大日志文件数量labels:# 为容器添加元数据标签-"key1=value1"# 标签键值对-"key2=value2"# 多个标签# 用户与权限user: 1000:1000# 运行容器的用户ID:组IDcap_add:# 添加Linux能力- NET_ADMIN # 网络管理能力- SYS_TIME # 系统时间修改能力cap_drop:# 删除Linux能力- ALL # 删除所有能力security_opt:# 安全选项- no-new-privileges:true# 禁止提升权限- seccomp:unconfined # 禁用seccomp限制privileged:false# 是否以特权模式运行group_add:# 添加补充组-"1001"# 组ID 1001-"1002"# 组ID 1002# 系统配置pid: host # 使用主机的PID命名空间ipc: shareable # IPC命名空间模式sysctls:# 修改内核参数- net.core.somaxconn=1024 # 最大连接队列长度- net.ipv4.tcp_syncookies=0 # TCP同步cookie设置stop_signal: SIGTERM # 停止容器时发送的信号stop_grace_period: 60s # 强制停止前的等待时间shm_size: 256mb # /dev/shm的大小# 运行时配置runtime: runc # 容器运行时isolation: default # Windows容器隔离模式platform: linux/amd64 # 目标平台pull_policy: always # 镜像拉取策略profiles:# 配置文件分组- dev # 开发环境配置文件- prod # 生产环境配置文件# 扩展配置extends:# 继承其他服务的配置file: common.yml # 来源文件service: base-service # 继承的服务名(5)networks(网络定义)
networks:# 定义所有网络的顶级指令network-name:# 网络名称,可自定义driver: bridge # 网络驱动类型:bridge/overlay/host/nonedriver_opts:# 驱动选项com.docker.network.bridge.name: br-custom # 自定义网桥名称com.docker.network.bridge.enable_icc:"true"# 允许容器间通信attachable:true# 是否允许独立容器连接到该网络enable_ipv6:true# 是否启用IPv6ipam:# IP地址管理配置driver: default # IPAM驱动config:# IPAM配置-subnet: 172.20.0.0/16 # 网络子网ip_range: 172.20.5.0/24 # 分配的IP范围gateway: 172.20.0.1 # 网关地址aux_addresses:# 辅助IP地址host1: 172.20.1.5 # 为主机1保留的IPhost2: 172.20.1.6 # 为主机2保留的IPinternal:true# 是否为内部网络(禁止外部访问)labels:# 为网络添加元数据标签-"key=value"# 标签键值对external:false# 是否已存在的外部网络name: custom-network-name # 自定义网络名称(6)volumes(数据卷定义)
volumes:# 定义所有数据卷的顶级指令volume-name:# 卷名称,可自定义driver: local # 卷驱动类型driver_opts:# 驱动选项type: nfs # 文件系统类型o: addr=192.168.1.100,rw,nfsvers=4 # 挂载选项device::/path/to/dir # 设备路径external:false# 是否已存在的外部卷labels:# 为卷添加元数据标签-"key=value"# 标签键值对name: custom-volume-name # 自定义卷名称(7)configs(配置文件定义)
configs:# 定义所有配置的顶级指令(Swarm模式)config-name:# 配置名称,可自定义file: ./config/file.yml # 从文件加载配置external:false# 是否已存在的外部配置name: custom-config-name # 自定义配置名称labels:# 为配置添加元数据标签-"key=value"# 标签键值对template_driver: golang # 模板驱动类型(8)secrets(密钥定义)
secrets:# 定义所有密钥的顶级指令(Swarm模式)secret-name:# 密钥名称,可自定义file: ./secrets/secret.txt # 从文件加载密钥external:false# 是否已存在的外部密钥name: custom-secret-name # 自定义密钥名称labels:# 为密钥添加元数据标签-"key=value"# 标签键值对template_driver: golang # 模板驱动类型(9)x-(自定义扩展字段)
x-logging:&logging-config# 自定义扩展字段,&定义锚点driver: json-file # 日志驱动options:# 日志选项max-size:"10m"# 日志文件最大大小max-file:"3"# 最大文件数x-env:&common-env# 通用环境变量配置NODE_ENV: production # 节点环境LOG_LEVEL: info # 日志级别API_VERSION: v1 # API版本x-resources:&resource-limits# 资源限制配置limits:# 资源上限cpus:'0.5'# CPU上限memory: 512M # 内存上限reservations:# 资源预留cpus:'0.25'# CPU预留memory: 256M # 内存预留x-health:&health-config# 健康检查配置healthcheck:# 健康检查定义test:["CMD","curl","-f",""]# 检查命令interval: 30s # 检查间隔timeout: 10s # 超时时间retries:3# 重试次数start_period: 40s # 启动等待时间(10)include(包含其他 Compose 文件)
include:# 包含其他Compose文件的顶级指令-path: ./docker-compose.base.yml # 基础配置文件路径env_file: ./base.env # 为包含的文件指定环境变量文件-path: ./docker-compose.prod.yml # 生产配置文件路径project_directory: ./prod # 项目目录路径-path: ./services/web.yml # Web服务配置文件-path: ./services/db.yml # 数据库服务配置文件(11) Docker compose 命令
#语法 # docker compose --help
Usage: docker compose [OPTIONS] COMMAND
Define and run multi-container applications with Docker.
Commands:
config Parse, resolve and render compose filein canonical formatcp Copy files/folders between a service container and the local filesystem
down Stop and remove containers, networks
events Receive real time events from containers.
logs View output from containers
ls List running compose projects
ps List containers
pull Pull service images
restart Restart service containers
top Display the running processes
up Create and start containers
version Show the Docker Compose version information
#启动项目[root@docker 01-compose-python-demo]# docker compose up -d [+] Running 3/3
✔ Network 01-compose-python-demo_default Created 0.0s
✔ Container 01-compose-python-demo-web-1 Started 0.1s
✔ Container 01-compose-python-demo-redis-1 Started 0.1s
#停止项目[root@docker 01-compose-python-demo]# docker compose down -v [+] Running 3/2
✔ Container 01-compose-python-demo-redis-1 Removed 0.1s
✔ Container 01-compose-python-demo-web-1 Removed 10.1s
✔ Network 01-compose-python-demo_default Removed 0.0s
#查看服务日志[root@docker 01-compose-python-demo]# docker compose logs
01-compose-python-demo-web-1 | * Serving Flask app 'app.py'
01-compose-python-demo-web-1 | * Debug mode: off
01-compose-python-demo-web-1 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
01-compose-python-demo-web-1 | * Running on all addresses (0.0.0.0)
01-compose-python-demo-web-1 | * Running on
01-compose-python-demo-web-1 | * Running on
# 查看指定服务的日志[root@docker 01-compose-python-demo]# docker compose logs web
01-compose-python-demo-web-1 | * Serving Flask app 'app.py'
01-compose-python-demo-web-1 | * Debug mode: off
01-compose-python-demo-web-1 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
docker compose 命令在执行时 必须在 compose.yaml 文件所在的目录下,
docker compose -f compose.yml up -d 默认省略了参数 -f compose.yml
15、优雅退出
(1)linux信号
#Kill 参数
信号是一种进程间通信的方法,应用于异步事件的处理。信号的实质是一种软中断。
使用kill -l可以查看Linux系统中的所有信号,如下:
[root@docker2 ~]# kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
信号是 Linux 内核与进程以及进程间通信的一种方式。针对每个信号进程都有个默认的动作,不过进程可以通过定义信号处理程序来覆盖默认的动作,除了 SIGSTOP 和 SIGKILL。二者都不能被捕获或重写,前者用来将进程暂停在当前状态,而后者则是从内核层面立即杀掉进程。
有两个比较重要的进程 SIGTERM 和 SIGKILL。SIGTERM 是优雅地关闭命令,SIGKILL 则是暴力的关闭命令。比如 Docker,容器会先收到 SIGTERM 信号,10s 后会收到 SIGKILL 信号。
| 信号值 | 信号名 | 说明 | 备注 |
|---|---|---|---|
| 1 | SIGHUP | 启动被终止的程序,可让该进程重新读取自己的配置文件,类似重新启动。 | |
| 2 | SIGINT | 相当于用键盘输入 [ctrl]-c 来中断一个程序的进行。 | interrupt |
| 9 | SIGKILL | 代表 强制中断 一个程序的进行,如果该程序进行到一半,那么尚未完成的部分可能会有“半产品”产生,类似 vim 会有 .filename.swp 保留下来。 | 此信号无法捕捉kill |
| 15 | SIGTERM | 以正常的方式来终止该程序。由于是正常的终止,所以后续的动作会将他完成。不过,如果该程序已经发生问题,就是无法使用正常的方法终止时,输入这个 signal 也是没有用的。 | terminate |
| 19 | SIGSTOP | 相当于用键盘输入 [ctrl]-z 来暂停一个程序的进行。 | 此信号无法捕捉stopped |
(2)容器中的信号
1)属于容器的 1 号进程
#容器中的进程属于容器的 1 号进程
Docker 的 stop 和 kill 命令都是用来向容器发送信号的。注意,只有容器中的 1 号进程能够收到信号,这一点非常关键!stop 命令会首先发送 SIGTERM 信号,并等待应用优雅的结束。如果发现应用没有结束(用户可以指定等待的时间),就再发送一个 SIGKILL 信号强行结束程序。docker kill 命令默认发送的是 SIGKILL 信号,当然你可以通过 -s 选项指定任何信号。
#app.js文件
'use strict';var http =require('http');var server = http.createServer(function(req, res){
res.writeHead(200,{'Content-Type':'text/plain'});
res.end('Hello World\n');}).listen(3000,'0.0.0.0');
console.log('server started');var signals ={'SIGINT':2,'SIGTERM':15};functionshutdown(signal, value){
server.close(function(){
console.log('server stopped by '+ signal);
process.exit(128+ value);});}
Object.keys(signals).forEach(function(signal){
process.on(signal,function(){shutdown(signal, signals[signal]);});});#package.json文件
{"name":"normalize.css","version":"3.0.3","description":"Normalize.css as a node packaged module","style":"normalize.css","files":["LICENSE.md","normalize.css"],"homepage":"","repository":{"type":"git","url":"git://github.com/necolas/normalize.css.git"},"main":"normalize.css","author":{"name":"Nicolas Gallagher"},"license":"MIT","gitHead":"2bdda84272650aedfb45d8abe11a6d177933a803","bugs":{"url":""},"_id":"normalize.css@3.0.3","scripts":{},"_shasum":"acc00262e235a2caa91363a2e5e3bfa4f8ad05c6","_from":"normalize.css@3.0.3","_npmVersion":"2.7.0","_nodeVersion":"0.10.35","_npmUser":{"name":"necolas","email":"nicolasgallagher@gmail.com"},"maintainers":[{"name":"tjholowaychuk","email":"tj@vision-media.ca"},{"name":"necolas","email":"nicolasgallagher@gmail.com"}],"dist":{"shasum":"acc00262e235a2caa91363a2e5e3bfa4f8ad05c6","tarball":""},"directories":{},"_resolved":"","readme":"ERROR: No README data found!"}#Dockerfile文件
FROM iojs:onbuild
COPY ./app.js ./app.js
COPY ./package.json ./package.json
EXPOSE 3000
ENTRYPOINT ["node", "app"]
#构建镜像、测试[root@docker dockerfile-signal]# docker build --no-cache -t signal-app -f Dockerfile .# 运行容器[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name="my-app" signal-app
server started
# 发送信号[root@docker dockerfile-signal]# docker container kill --signal="SIGTERM" my-app
my-app
# 收到 SIGTERM 信号 退出[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name="my-app" signal-app
server started
server stopped by SIGTERM
2)不属于容器的1号进程
#创建 app1.sh 文件#!/bin/bashnode app
#创建 Dockerfile1 文件
FROM iojs:onbuild
COPY ./app.js ./app.js
COPY ./app1.sh ./app1.sh
COPY ./package.json ./package.json
RUN chmod +x ./app1.sh
EXPOSE 3000
ENTRYPOINT ["./app1.sh"][root@docker dockerfile-signal]# docker build --no-cache -t signal-app1 -f Dockerfile1 .[root@docker dockerfile-signal]# docker container kill --signal="SIGKILL" my-app1[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name="my-app1" signal-app1
server started
3)在脚本中捕获信号
1、创建 app2.sh 文件
#!/bin/bash# 打开调试级别set-x# 指定当前 pid 号pid=0# SIGUSR1-handlermy_handler(){echo"my_handler"}# SIGTERM-handlerterm_handler(){if[$pid-ne0];thenkill-SIGTERM"$pid"# wait是用来阻塞当前进程的执行,直至指定的子进程执行结束后,才继续执行wait"$pid"fiexit143;# 128 + 15 -- SIGTERM}# setup handlers# on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler# trap 'commands' signal-list 当脚本收到 signal-list 清单内列出的信号时, trap 命令执行双引号中的命令trap'kill ${!}; my_handler' SIGUSR1
trap'kill ${!}; term_handler' SIGTERM
# run applicationnode app &pid="$!"# wait foreverwhiletruedotail-f /dev/null &wait${!}done2、创建 Dockerfile2 文件
FROM iojs:onbuild
COPY ./app.js ./app.js
COPY ./app2.sh ./app2.sh
COPY ./package.json ./package.json
RUN chmod +x ./app2.sh
EXPOSE 3000
ENTRYPOINT ["./app2.sh"]3、构建镜像&测试
[root@docker dockerfile-signal]# docker build --no-cache -t signal-app2 -f Dockerfile2 .[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name="my-app2" signal-app2
+ pid=0
+ trap'kill ${!}; my_handler' SIGUSR1
+ trap'kill ${!}; term_handler' SIGTERM
+ pid=7
+ true
+ node app
+ wait8
+ tail-f /dev/null
server started
[root@docker dockerfile-signal]# docker container kill --signal="SIGTERM" my-app2
my-app2
[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name="my-app2" signal-app2...
server started
++ kill8
++ term_handler
++ '['7-ne0']'
++ kill-SIGTERM7
++ wait7
server stopped by SIGTERM
++ exit1434) 使用 tini 作为容器启动入口
tini 是一套更简单的 init 系统,专门用来执行一个子程序(spawn a single child),并等待子程序结束,即便子程序已经变成僵尸程序也能捕捉到,同时也能转送 Signal 给子程序。如果你使用docker来跑容器,可以非常简便的在docker run的时候用 --init 参数,就会自动注入tini程式 (/sbin/docker-init) 到容器中,并且自动取代ENTRYPOINT设定,让原本的程式直接跑在 tini程序底下。
注意:Docker 1.13 以后的版本开始支持 --init 参数,并內建 tini 在內。
[root@docker dockerfile-signal]# docker run --rm --init --name my-app signal-app
server started
[root@docker ~]# docker container kill --signal="SIGTERM" my-app
my-app
[root@docker dockerfile-signal]# docker run --rm --init --name my-app signal-app
server started
server stopped by SIGTERM
ild --no-cache -t signal-app1 -f Dockerfile1 .
[root@docker dockerfile-signal]# docker container kill --signal=“SIGKILL” my-app1
[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name=“my-app1” signal-app1
server started
### 3)在脚本中捕获信号
```bash
1、创建 app2.sh 文件
#!/bin/bash
# 打开调试级别
set -x
# 指定当前 pid 号
pid=0
# SIGUSR1-handler
my_handler() {
echo "my_handler"
}
# SIGTERM-handler
term_handler() {
if [ $pid -ne 0 ]; then
kill -SIGTERM "$pid"
# wait是用来阻塞当前进程的执行,直至指定的子进程执行结束后,才继续执行
wait "$pid"
fi
exit 143; # 128 + 15 -- SIGTERM
}
# setup handlers
# on callback, kill the last background process, which is `tail -f /dev/null` and execute the specified handler
# trap 'commands' signal-list 当脚本收到 signal-list 清单内列出的信号时, trap 命令执行双引号中的命令
trap 'kill ${!}; my_handler' SIGUSR1
trap 'kill ${!}; term_handler' SIGTERM
# run application
node app &
pid="$!"
# wait forever
while true
do
tail -f /dev/null & wait ${!}
done
2、创建 Dockerfile2 文件
FROM iojs:onbuild
COPY ./app.js ./app.js
COPY ./app2.sh ./app2.sh
COPY ./package.json ./package.json
RUN chmod +x ./app2.sh
EXPOSE 3000
ENTRYPOINT ["./app2.sh"]
3、构建镜像&测试
[root@docker dockerfile-signal]# docker build --no-cache -t signal-app2 -f Dockerfile2 .
[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name="my-app2" signal-app2
+ pid=0
+ trap 'kill ${!}; my_handler' SIGUSR1
+ trap 'kill ${!}; term_handler' SIGTERM
+ pid=7
+ true
+ node app
+ wait 8
+ tail -f /dev/null
server started
[root@docker dockerfile-signal]# docker container kill --signal="SIGTERM" my-app2
my-app2
[root@docker dockerfile-signal]# docker run -it --rm -p 3000:3000 --name="my-app2" signal-app2
...
server started
++ kill 8
++ term_handler
++ '[' 7 -ne 0 ']'
++ kill -SIGTERM 7
++ wait 7
server stopped by SIGTERM
++ exit 143
4) 使用 tini 作为容器启动入口
tini 是一套更简单的 init 系统,专门用来执行一个子程序(spawn a single child),并等待子程序结束,即便子程序已经变成僵尸程序也能捕捉到,同时也能转送 Signal 给子程序。如果你使用docker来跑容器,可以非常简便的在docker run的时候用 --init 参数,就会自动注入tini程式 (/sbin/docker-init) 到容器中,并且自动取代ENTRYPOINT设定,让原本的程式直接跑在 tini程序底下。
注意:Docker 1.13 以后的版本开始支持 --init 参数,并內建 tini 在內。
[root@docker dockerfile-signal]# docker run --rm --init --name my-app signal-app
server started
[root@docker ~]# docker container kill --signal="SIGTERM" my-app
my-app
[root@docker dockerfile-signal]# docker run --rm --init --name my-app signal-app
server started
server stopped by SIGTERM
版权声明:本文标题:轻松掌握 Docker 容器,开启你的容器化之旅 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.betaflare.com/biancheng/1772303461a3273334.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。


发表评论