企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 笔记 [TOC=3,8] ---- ### 镜像包含什么? - 操作系统 root 根文件和目录 - 包含运行程序依赖的环境及其配置,如 sdk、php、python - 包含完整的运行程序(如 源程序、应用配置等 以及 依赖的三方包和库) ---- ### 应该选择什么版本的基础镜像? 如果不知道该选择哪个发行版,那么镜像大小就是最主要的参考,考虑到部署分发,容器当然是越小越好。 这里有一个参考: php:7.4.33-cli-alpine3.16(83.5MB) 比 7.4.33-cli-bullseye(474MB) 要小5倍(要看下载解压到本地时的空间占用),但却提供了常用的命令 vi curl nc 等,简直是专门为容器而生的版本啊。 ---- ### 理解 Dockerfile **Dockerfile 是操作记录** Dockerfile 的本质是一个包含创建镜像时的每一行命令记录的文件。有了操作记录,那么镜像的创建过程就不再是一个黑盒了。 如此设计的更重要的意义还在于由于操作记录是固定的,那么这个过程就是可重现的,这是 docker 一次构建多处运行的基础,也是 docker 受欢迎的主要原因。 当镜像被其它镜像所依赖时,只需要在构建好成品 ———— 镜像本体上接着操作就行了,而不必再重复执行构建过程,如果想知道所依赖镜像构成细节,查看其 Dockerfile 文件就行了,因为这一切都是透明的,所以大家相互共享镜像就没有信任的问题了。 > 需要注意的是: ENTRYPOINT CMD 为运行容器时默认的主进程启动命令,只会在运行容器时执行,构建镜像时不会执行,如果被作为依赖镜像时,也不会执行,而是执行当前启动镜像的命令。但如果当前容器镜像没有 CMD 或 ENTRYPOINT 指令,那么就会执行其依赖镜像的启动命令。(若在当前层重写了上层的 ENTRYPOINT, 那么 CMD 也必须重写,否则不会继承过来,换句话说 每层的 ENTRYPOINT CMD 是相互成对的 ) 多层镜像依赖其实是多个镜像层而已,而 对于命令 ENTRYPOINT CMD, 后层的命令会覆盖前一层的命令。 > 注意 如果有 sh 脚本,一定要检查行尾是否为 Unix ,否则构建时可能出现意外的失败。(Sublime Text: 查看 > 行尾 > Unix) ---- **Dockerfile 每一行命令都是一个层** 镜像并非是一个像 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 所以写每一行时需要考虑缓存的问题。 ---- **如何理解 FROM** Dockerfile 从 FROM 开始,表示 从一个环境开始,后面的 上下文语境即都是已进入到这个环境下了。 FROM 是进入环境的入口,这个入口可以是 一个 OS 发行版,也可以是 scratch 空白的镜像。 即 FROM 后的所有命令都是在其源环境内了,所以每一行操作都会直接影响构建环境,即影响最终构建的镜像。 ---- ### 构建最快的镜像: 多阶段构建 多阶段构建 只是将 原本需要创建多个 Dockerfile 的构建项目,如 从前一个镜像中复制文件到后一层镜像中 等过程简化了,将其合并成一个 Dockerfile 文件,方便构建操作而已,本质上还是要构建多个镜像。 多阶段的每一阶段都从 FROM 开始,最终的 镜像 只会从 最后一个阶段构建,不会包含前面阶段产生的层,因此可以减少镜像的体积。 就像预制菜可以直接由半成品加热就可以吃,而不用买菜洗菜的环节。 ---- ### 镜像 通常我们所说的镜像具体是指某个软件仓库的某个版本,即 `仓库名:tag` ,如 `ubuntu:18.04` , `xiaobu191/php:7.4.33-cli` **Tag** 通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 `<仓库名>:<标签>`的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 `latest` 作为默认标签。 **OS/ARCH** 一个 tag标签 对应 一个镜像,一个镜像 可对应多个不同 OS/ARCH (操作系统/硬件结构), 每个都有一个唯一的 DIGEST 值(sha256 摘要),即每个镜像在特定平台下都有一个镜像文件。 **IMAGE ID** 同一镜像(同一软件、仓库)的 不同 Tag 版本 的镜像ID 是相同的,因为它们对应的是同一个镜像。 镜像的 唯一标识 除了 镜像ID 还有 镜像摘要,其实 也是 `镜像:tag:OS/ARCH` 的摘要。 他们的关系如下: ~~~ 镜像 ubuntu 97ba4bbc97fc tag1 ubuntu:18.04 x86 DIGEST:sha256:2852f36559cee4a83bf2a5102d91bde01826c2516fb9e78f3981775acf381292 arm64 ... tag2 x86 ... ... ~~~ ---- ### 运行容器 **容器只能包含一个“进程”?** 首先说明的是这个进程是打引号的,并不是指一个容器中只能运行一个进程。 在解释这个问题之前先了解一下背景: 宿主系统的 1号进程 是所有进程的父进程,通常是 `/usr/lib/systemd/systemd`,这个父进程会托管照管所有进程(详细可了解 孤儿进程和僵尸进程的相关知识), 在 docker 中 查看 1号进程会发现它就是我们的 容器入口程序,这个 1号进程 就是我们的容器进程, docker 通过判断这个进程的状态来判断容器是否启动。 此外这个进程需要承担 托管其它进程的责任,所以说容器只能包含一个“进程”指的是容器内只能有一个这样的1号进程,如 nginx 、workerman 这种 master/worker 的进程模型。 如果我们的容器是由多个独立的进程,不是这种进程模型怎么办?也是有办法的,可以使用 supervisor , pm2 这类工具管理进程,如把 pm2 当成 容器入口程序就行了,否则的话 docker 容器将无法按预期正常的管理进程。 > 对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。 ~~~ $: docker run -it --rm ubuntu:18.04 bash # 上面的命令的进程模型为: /usr/lib/systemd/systemd /usr/sbin/sshd sshd -zsh docker run -it --rm ubuntu:18.04 bash # 容器中启动的 1号进程 bash 的 进程架构: /usr/lib/systemd/systemd /usr/bin/containerd-shim-runc-v2 bash ~~~ ~~~shell $: docker run -it --rm ubuntu:18.04 bash ps -aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 18504 2040 pts/0 Ss 05:56 0:00 bash root 16 0.0 0.0 34400 1508 pts/0 R+ 06:01 0:00 ps -aux ~~~ ~~~shell $: docker run --name webserver -d -p 8080:80 nginx $: docker exec -it webserver bash $: apt-get update $: apt-get install procps $: ps -aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 9720 3528 ? Ss 05:46 0:00 nginx: master process nginx -g daemon off; nginx 29 0.0 0.0 10116 1772 ? S 05:46 0:00 nginx: worker process nginx 30 0.0 0.0 10116 2028 ? S 05:46 0:00 nginx: worker process nginx 31 0.0 0.0 10116 2024 ? S 05:46 0:00 nginx: worker process nginx 32 0.0 0.0 10116 1784 ? S 05:46 0:00 nginx: worker process root 33 0.0 0.0 4144 2184 pts/0 Ss 05:50 0:00 bash root 402 0.0 0.0 6740 1480 pts/0 R+ 06:01 0:00 ps -aux ~~~ 宿主机上 ~~~shell $: ps -aux | grep bash root 5151 0.1 0.5 1027444 28332 pts/0 Sl+ 13:50 0:01 docker exec -it webserver bash root 5170 0.0 0.0 4144 2188 pts/0 Ss+ 13:50 0:00 bash $: ps -aux | grep nginx root 4852 0.0 0.0 9720 3528 ? Ss 13:46 0:00 nginx: master process nginx -g daemon off; 101 4908 0.0 0.0 10116 1772 ? S 13:46 0:00 nginx: worker process 101 4909 0.0 0.0 10116 2028 ? S 13:46 0:00 nginx: worker process 101 4910 0.0 0.0 10116 2024 ? S 13:46 0:00 nginx: worker process 101 4911 0.0 0.0 10116 1784 ? S 13:46 0:00 nginx: worker process root 5869 0.0 0.0 112824 1004 pts/1 S+ 14:07 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox ngin $: ps 4831 PID TTY STAT TIME COMMAND 4831 ? Sl 0:02 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fe0efd3de7c27588db1ec86d31aaebd6075d10059ff8652d877de94e2897060b -address /run/containerd/containerd.sock ~~~ 1. 容器里面的 nginx: master 和 进入的 bash pid 都是0 2. 他们对应宿主上的 pid 为:1 => 4852, 33 => 5170 3. 容器内的 主进程 不需要被托管(毕竟容器内的进程是虚拟的) 需要注意的是,这里的 nginx 是在 docker-entrypoint.sh 入口点程序中使用 exec 启动 `nginx -g daemon off;` 命令的, 因为 `exec` 命令会 替代当前进程(这里的 sh 进程),所以 nginx 的父进程不是 sh 进程(它直接取待了 启动它的sh),而其自身也就成了主进程作为容器内的 1号进程。 **容器主进程退出后其它进程会怎么样?** 待实验,猜测:容器是为主进程而存在的,如果主进程退出,此时容器中还有其它进程,那么会被 dockerd 强制 kill 掉。 ---- ### 数据卷 数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性: - 数据卷是宿主主机中的目录,挂载点为容器内目录 - 数据卷可以在容器间共享和重用 - 对数据卷的修改会立即生效 - 对数据卷的更新,不会影响镜像 - 数据卷是被设计用来持久化数据的,它的生命周期独立于容器 - 数据卷默认会一直存在,即使容器被删除 - 数据卷的使用,类似于 Linux 下对目录或文件进行挂载(mount) - 镜像中被指定为挂载点的目录中的文件会复制到数据卷中(仅数据卷为空时会复制) ~~~ 挂载细节: 1. docker run --rm -it -v /yf_api/php.ini:/usr/local/etc/php/php.ini xiaobu191/yf_api sh -v 无法将容器内的文件/目录暴露出来,只能是宿主覆盖到容器中(宿主 => 容器),并且宿主上文件必须存在(否则会失败,并在宿主上生成一个目录而不是文件) 2. 可以将容器内的目录复制暴露到数据卷中(非宿主自定义目录),仅数据卷为空时,否则卷目录会覆盖容器中的目录 3. 所以如果想将容器内文件暴露出来,只能 docker cp <容器ID或名称>:/path/to/container/file /path/to/host/ ~~~ **VOLUME** ``` # 1. Dockerfile 中指定目录 挂载为 匿名卷 VOLUME /data ``` Docker 容器原则上是临时、无状态的,不应该在容器内(容器存储层)写入大量数据,为了防止其在容器存储层写入数据, 可以用 VOLUME 命令 事先指定容器内某些目录挂载为 匿名卷(容器外): /var/lib/docker/volumes/4f2d4...d27652/_data ,这样容器运行时数据就保存外部 匿名卷的位置了,容器停止并销毁时数据也不会丢失。 如果不使用卷和挂载,那么默认容器运行时数据会写入到容器存储层: /var/lib/docker/overlay2/d7d2...182/ diff merged work 容器日志 /var/lib/docker/containers/容器id/容器id-json.log (docker inspect --format='{{.LogPath}}' 容器id or 容器名) ```shell # 创建命名卷 # /var/lib/docker/volumes/mydata/_data docker volume create mydata # 2. 容器运行时 指定目录挂载为 命名卷 docker run -d -v mydata:/data xxxx # 3. 容器运行时 指定容器内目录挂载为主机目录 docker run -d -v /data:/data xxxx ``` ~~~ $: docker volume $: docker volume inspect mydata [ { "CreatedAt": "2023-06-04T09:40:50+08:00", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/mydata/_data", "Name": "mydata", "Options": {}, "Scope": "local" } ] ~~~ ~~~ $: docker run --rm -it --name php -v mydata:/data xiaobu191/php-7.4.33-cli sh $: docker inspect php ... "Mounts": [ { "Type": "volume", "Name": "mydata", "Source": "/var/lib/docker/volumes/mydata/_data", "Destination": "/data", "Driver": "local", "Mode": "z", "RW": true, "Propagation": "" } ], ... ~~~ ~~~ ... $: docker run --rm -it --name php -v /mnt/web:/web xiaobu191/php-7.4.33-cli sh $: docker inspect php "Mounts": [ { "Type": "bind", "Source": "/mnt/web", "Destination": "/web", "Mode": "", "RW": true, "Propagation": "rprivate" } ], ... ~~~ ~~~ $: docker run --rm -it --name php --mount type=bind,source=/mnt/web,target=/web xiaobu191/php-7.4.33-cli sh $: docker inspect php ... "Mounts": [ { "Type": "bind", "Source": "/mnt/web", "Destination": "/web", "Mode": "", "RW": true, "Propagation": "rprivate" } ], ... ~~~ ~~~ $: docker run --rm -it --name php --mount type=tmpfs,target=/web xiaobu191/php-7.4.33-cli sh $: docker inspect php "Mounts": [ { "Type": "tmpfs", "Source": "", "Destination": "/web", "Mode": "", "RW": true, "Propagation": "" } ], ~~~ 也可以在启动容器时 通过 `-v` 参数指定 命名卷 或 目录 挂载到 容器内的目录上。 将容器外的 目录 直接映射到 容器内的目录上 常用于宿主与容器共享文件 或 测试,如放置一些文件到本地,映射到容器中看是否正常工作 而使用 卷 常用于规范容器运行时数据保存。 **`--mount` 与 `-v` 的去区别?** 1. 都是主机目录挂载到容器内的目录,只是挂载类型不同: `--mount`: Mounts.Type: bind `-v`: Mounts.Type: volume --mount 更灵活,可以指定挂载类型: bind, volume, tmpfs 2. `-v` 本地目录不存在 Docker 会自动为你创建一个文件夹, `--mount` 本地目录不存在,Docker 会报错。 https://vuepress.mirror.docker-practice.com/data_management/bind-mounts/#查看数据卷的具体信息 https://blog.csdn.net/gongdiwudu/article/details/128756465 ~~~ 挂载细节(重要): - 本地目录/文件(或卷) 挂载到 容器中,本地优先、卷优先,原则上 容器是临时的 - 如果 本地有(或卷),那么就会 映射、覆盖到容器中,并可选 容器内 对其 是否可写 - 如果 本地没有(或卷),容器内有,那么 会将容器中的文件 复制到 本地 - 如果 都没有,那么双方写入都相互可见 ~~~ ... ---- ### 容器空间占用 [如何清理 docker 磁盘空间 附讲解(全)_docker 清理_码农研究僧的博客-CSDN博客](https://blog.csdn.net/weixin_47872288/article/details/128244770) https://www.codetd.com/article/14982610 https://tool.4xseo.com/a/11941.html ```shell $: docker system df $: docker system df -v # 清除没有被容器使用的镜像 $: docker image prune -af # 清除未被使用的 volume 等 $: docker system prune -f $: docker system prune -a # 容器镜像、运行时目录 merged 、 diff 、 work /var/lib/docker/overlay2 /var/lib/docker/overlay2/7078...f12/diff/root/.pm2 # 容器日志目录 容器id/容器id-json.log /var/lib/docker/containers # k8s 日志 /var/log/containers /var/log/pods # 匿名卷、命名卷 /var/lib/docker/volumes ``` https://blog.csdn.net/czf19950601/article/details/125064452 ```shell $: vi /etc/docker/daemon.json { "log-driver": "json-file", "log-opts": { "max-size": "256m", "max-file": "12", "compress": "true" } } $: sudo systemctl daemon-reload $: sudo systemctl restart docker.service $: docker inspect yf-web-phpfpm $: docker inspect -f {{.HostConfig.LogConfig}} yf-web-phpfpm $: docker info --format '{{.LoggingDriver}}' ``` https://blog.51cto.com/u_14035463/5583658 ---- **workerman 日志问题** pm2 就不需要记录日志了,因为 workerman 自己已经记录到文件了,不然会记录三份(容器输出日志、/root/.pm2/logs、workerman 日志) ~~~ 1. 前台时,不允许设置 STDOUT 2. 前台时,log() 同时会输出到 STDOUT 我们是后台设置了 STDOUT 为 logFile,所以 不满足 2 ,不会写两次到日志文件 如果是前台,那么又设置不了 STDOUT, 2 同时输入到 STDOUT ,会写到终端输出和 docker log ,也不会 写两次 日志文件 所以不论是前后还是后台,都不会出现写两次 workerman 日志文件的情况。 ~~~ ---- ### 容器网络 **容器互联** Docker 允许通过 外部访问容器 或 容器互联 的方式来提供网络服务。 ```shell $: cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.17.0.2 266aca625bb7 $: ping 266aca625bb7 PING 266aca625bb7 (172.17.0.2): 56 data bytes 64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.183 ms 64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.093 m ``` 可以看到 docker 在容器启动后自动其创建了一个 主机名(通过自动修改 hosts),名称为当前的容器名称或容器id,这样在同一容器网络下的容器间可以通过容器名作为 主机名/服务名 来相互访问了。(容器把自己的名称写到自己的 hosts 中,而 docker 底层把加入到同一网络的容器做了特殊处理,使其 hosts “相互融合了”) 1. 创建 Docker 网络 ```shell $: docker network create -d bridge my-net $: docker network ls ``` 2. 连接到 Docker 网络 ``` $: docker run --rm -it --name php --network my-net xiaobu191/php-7.4.33-cli sh $: docker run --rm -it --name php2 --network my-net xiaobu191/php-7.4.33-cli sh $: docker run --rm -it --name php3 xiaobu191/php-7.4.33-cli sh ``` 在上面两个容器终端里相互 `ping php` 或 `ping 31851287fcc8` (对方的容器名或容器id) 都能成功解析到虚拟ip地址,而在第三个终端里 `ping` 不通其它容器名称和容器id,并且自身也不能被其它容器 `ping` 通,这证明了加入到 Docker 网络 的容器可以实现互联。 > 需要注意的是,容器互联的端口是一个容器直接连另一个容器的内部端口 **外部访问容器** ~~~ # -P 标记时,Docker 会随机映射一个端口到内部容器开放的网络端口(如 EXPOSE 80) -P -p 容器外部:容器内部 ~~~ 1. 映射所有ip地址 使用 hostPort:containerPort 格式本地的 80 端口映射到容器的 80 端口: ```shell $: docker run -d -p 80:80 nginx:alpine ``` 这也是用的最多的方式,`-p` 标记也可以多次使用来绑定多个端口。 2. 映射指定ip地址的指定端口 可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1 ```shell $: docker run -d -p 127.0.0.1:80:80 nginx:alpine ``` 3. 映射指定ip地址的随机端口 使用 ip::containerPort 绑定 localhost 的任意端口到容器的 80 端口,本地主机会自动分配一个端口。 ```shell $: docker run -d -p 127.0.0.1::80 nginx:alpine ``` ```shell # 还可以使用 udp 标记来指定 udp 端口 $: docker run -d -p 127.0.0.1:80:80/udp nginx:alpine # 查看映射端口配置 $: docker port 容器id 80 0.0.0.0:32768 ``` **为容器配置 DNS** DNS 服务器 用来 解析不在 /etc/hosts 中的主机名,所以 hosts 文件可以用于配置跳过 DNS 服务器解析而 直接指向的IP 的 地址名(域名或主机地址)。 https://vuepress.mirror.docker-practice.com/network/dns/ ```shell cat /etc/hosts (host 文件) cat /etc/hostname (主机名) ``` ```shell $: cat /etc/resolv.conf (DNS) # Generated by NetworkManager nameserver 114.114.114.114 nameserver 8.8.8.8 nameserver 1.1.1.1 ``` 目前发现没加入 Docker 网络 的容器反而自动继承了宿主机的 DNS 配置了,而加入了 Docker 网络 的容器呢则没有宿主机的 DNS 了: ``` $: cat etc/resolv.conf nameserver 127.0.0.11 options ndots:0 ``` 还可以通过 `-h HOSTNAME` 或者 `--hostname=HOSTNAME` 设定容器的主机名,也可以通过 `--dns=IP_ADDRESS` 设置容器的 DNS 服务器 ---- ### 高级网络配置 ---- ### 容器组 Compose Docker Compose 允许用户通过一个单独的 docker-compose.yml 模板文件来定义一组相关联的应用容器为一个项目,这样在生产中就能提高多容器部署的效率。 Compose 中有两个重要的概念: 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例(多个相同应用节点)。 项目 (project):由一组关联的应用容器组成的一个完整业务单元(多个服务),在 docker-compose.yml 文件中定义。 Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。 ```yaml version: '3' services: phpfpm: image: "xiaobu191/php-7.4.33-fpm" ports: - "9000:9000" nginx: image: "xiaobu191/nginx" ports: - "80:80" ``` ```shell # 生产环境下可使用 -d 在后台启动 $: docker compose up [+] Running 3/3 ⠿ Network compose_default Created 0.2s ⠿ Container compose-nginx-1 Created 0.2s ⠿ Container compose-phpfpm-1 Created 0.2s Attaching to compose-nginx-1, compose-phpfpm-1 compose-phpfpm-1 | [05-Jun-2023 07:47:55] NOTICE: fpm is running, pid 1 compose-phpfpm-1 | [05-Jun-2023 07:47:55] NOTICE: ready to handle connections compose-nginx-1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration compose-nginx-1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ compose-nginx-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh compose-nginx-1 | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf compose-nginx-1 | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf compose-nginx-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh compose-nginx-1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh compose-nginx-1 | /docker-entrypoint.sh: Configuration complete; ready for start up compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: using the "epoll" event method compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: nginx/1.25.0 compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: built by gcc 12.2.1 20220924 (Alpine 12.2.1_git20220924-r4) compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: OS: Linux 3.10.0-1160.76.1.el7.x86_64 compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: start worker processes compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: start worker process 30 compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: start worker process 31 compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: start worker process 32 compose-nginx-1 | 2023/06/05 07:47:55 [notice] 1#1: start worker process 33 ``` ~~~ 常用参数 -d 在后台运行服务容器。 --no-color 不使用颜色来区分不同的服务的控制台输出。 --no-deps 不启动服务所链接的容器。 --force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。 --no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。 --no-build 不自动构建缺失的服务镜像。 -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。 ~~~ ``` $: docker compose top compose-nginx-1 UID PID PPID C STIME TTY TIME CMD root 3410 3376 0 15:47 ? 00:00:00 nginx: master process nginx -g daemon off; 101 3531 3410 0 15:47 ? 00:00:00 nginx: worker process 101 3532 3410 0 15:47 ? 00:00:00 nginx: worker process 101 3533 3410 0 15:47 ? 00:00:00 nginx: worker process 101 3534 3410 0 15:47 ? 00:00:00 nginx: worker process root 3786 3376 0 15:53 pts/0 00:00:00 sh compose-phpfpm-1 UID PID PPID C STIME TTY TIME CMD root 3399 3354 0 15:47 ? 00:00:00 php-fpm: master process (/usr/local/etc/php-fpm.conf) 82 3500 3399 0 15:47 ? 00:00:00 php-fpm: pool www 82 3501 3399 0 15:47 ? 00:00:00 php-fpm: pool www root 3829 3354 0 15:59 pts/0 00:00:00 sh ``` > 宿主上并没有 82 www-data 这个用户 ```shell $: docker compose ps $: docker network ls ``` 可以看到启动了两个容器 `compose-phpfpm-1`、`compose-nginx-1`,并默认创建了名为 `compose_default` 的 Docker 网络,两个容器也加入到了该网络中。 进入两个容器后能通过 容器id 或 容器名 或 服务名 相互 `ping` 通对方: ```shell $: ping compose-nginx-1 $: ping nginx $: nc -z -w 1 compose-nginx-1 80 && echo 1 1 $: nc -z -w 1 nginx 80 && echo 1 1 ``` ```shell $: nc -z -w 1 compose-phpfpm-1 9000 && echo 1 1 ``` 不过要注意的时,两个容器是相互独立的,nginx 中并不能 `nc` 通 `127.0.0.1 9000`,所以配置文件中 `fastcgi_pass phpfpm:9000` 应该使用服务名。 ```shell # 通过服务名暂停 (不同于 stop ,进程并没有停止,nc 端口也是通的,但是 curl 不响应了) # 它利用了cgroups的特性将运行中的进程空间暂停 https://cloud.tencent.com/developer/article/2108558 $: docker compose pause nginx # 恢复处于暂停状态中的服务 $: docker compose unpause nginx ``` ---- ### 用户权限相关 ``` # 查看所有用户信息 $: cat /etc/passwd # 查看某个用户详细 $: id www-data uid=82(www-data) gid=82(www-data) groups=82(www-data),82(www-data) # 查看所有用户 $: compgen -u ``` ``` # https://github.com/docker-library/php/blob/b9f17156020c3aef71df681b27684533529347a7/7.4/alpine3.16/fpm/Dockerfile#L33 $: adduser -u 82 -D -S -G www-data www-data $: chown www-data:www-data /var/www/html $: chmod 777 -R /var/www/html ``` ---- ### 容器健康检查 与 信号处理 ```shell # https://vuepress.mirror.docker-practice.com/compose/commands/#kill $: docker compose kill -s SIGINT ``` ~~~ # https://vuepress.mirror.docker-practice.com/image/dockerfile/healthcheck/ HEALTHCHECK --interval=5s --timeout=3s \ CMD curl -fs http://localhost/ || exit 1 ~~~ ~~~ # https://github.com/docker-library/php/blob/b9f17156020c3aef71df681b27684533529347a7/7.4/alpine3.16/fpm/Dockerfile#L255 STOPSIGNAL SIGQUIT ~~~ ---- ### 应用 docker 化改造 EXPOSE 80 9390 # 先指定目录挂在为匿名卷 VOLUME /home/myweb/apps_share_data # 创建命名卷 docker volume apps_share_data /home/myweb/apps_share_data # 将命名卷挂载到某个目录,替代 Dockerfile 中定义的匿名卷的挂在位置 run ... -v apps_share_data:/home/myweb/apps_share_data service: nginx: 80:80 admin.yf5g.cn admin.api.yf5g.cn admin.test.yf5g.cn admin.api.test.yf5g.cn php-fpm: 9000:9000 vue: php-cli: workerman gatewayworker php-fpm vue 需要把 源码 暴露给 nginx 的 root 配置使用,如何做到?用 VOLUME 卷吗? https://www.imooc.com/wenda/detail/399719 docker build -t xiaobu191/laravel --target=yf_api . docker build -t xiaobu191/nginx --target=nginx . $: docker network create yf_api $: docker run -dit --rm --name=yf_api --network=yf_api xiaobu191/yf_api $: docker run -dit --rm --name=nginx --network=yf_api -p 8080:80 xiaobu191/nginx ---- 目前 yf 服务器上的 /usr/local/php/sbin/php-fpm --daemonize --fpm-config /usr/local/php/etc/php-fpm.conf --pid /usr/local/php/var/run/php-fpm.pid /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf -g 'daemon off;' ---- 标准 php-fpm nginx -g 'daemon off;' 配置全部都用了默认配置,我们可能需要根据情况在 入口点 脚本中进行 调整,可参考 https://github.com/docker-library/php/blob/b9f17156020c3aef71df681b27684533529347a7/7.4/bullseye/fpm/Dockerfile https://github.com/nginxinc/docker-nginx/blob/3591b5e431af710432bd4852d9ee26eb19992776/mainline/debian/Dockerfile ---- /usr/local/nginx/sbin/nginx -V /usr/local/php/sbin/php-fpm --version 服务管理 ls /etc/rc.d/init.d -ls vi /etc/rc.d/init.d/nginx vi /etc/rc.d/init.d/php-fpm vi /etc/rc.d/init.d/yf-autostart.sh service nginx reload systemctl reload php-fpm.service ls /etc/systemd/system/ -ls systemctl start filebeat ---- ### 多前端应用部署方案 1. 共用一个 nginx 容器 2. 每个前端应用提供自己的 虚拟主机配置文件 3. 应用 的 静态文件 和 配置文件 在 nginx 启动时就挂在进去 > 在一个节点上尽量不启动多个 nginx 容器,前端项目部署应尽可能简单,甚至根本用不着容器。 ``` $: docker run --name=static-app -v nginx-conf:/etc/nginx/conf.d static-app $: docker create --name=static-app -v /root/conf.d:/etc/nginx/conf.d static-app ``` ``` # 先尝试采用通过宿主的卷以共享文件的方式(不可行) $: docker volume create apps $: docker volume create nginx-conf $: ls /var/lib/docker/volumes/apps/_data -ls $: ls /var/lib/docker/volumes/nginx-conf/_data -ls $: docker run --rm -it --name phpfpm --network phpfpm-network xiaobu191/php-api # 可以看到 php-api-nginx 镜像中的 /etc/nginx/conf.d 目录被复制到了 nginx-conf 卷中 $: docker run --rm -it --name nginx -v nginx-conf:/etc/nginx/conf.d -v apps:/home/myweb/apps --network phpfpm-network xiaobu191/php-api-nginx # 现在我们只需要 将其它 应用 静态文件 也这样复制到 apps 卷中就行了,这样 nginx 容器中就有这些配置了 # 将 容器内的 虚机配置 复制到 nginx-conf 卷中 # 将 容器内的 静态文件 复制到 apps 卷中 $: docker run \ -v nginx-conf:/home/myweb/apps/static/static.conf \ -v apps:/home/myweb/apps/ \ xiaobu191/static-app ``` ``` # Host 的数据复制到容器内部(这可能是前端项目部署最可行的方案) $: docker cp static-app/admin.yf5g.cn.conf php-api-nginx:/etc/nginx/conf.d/ $: docker cp static-app/public php-api-nginx:/home/myweb/apps/static-app # nginx 进程重载 $: docker kill -s SIGHUP nginx $: kill -HUP `docker inspect -f '{{.State.Pid}}' nginx || echo -n "nginx can't reload" ``` ```shell $: docker run -d \ --name php-api-nginx \ -p 80:80 \ --network phpfpm-network \ # 当需要替换容器内配置时 # -v /path-on-host-machine/nginx.conf:/etc/nginx.conf \ # -v /path-on-host-machine/admin.api.yf5g.cn.conf:/etc/nginx/conf.d/ \ xiaobu191/php-api-nginx ``` https://www.imooc.com/wenda/detail/399719 https://blog.csdn.net/IT_ZRS/article/details/126763116 ~~~ 镜像、容器中的文件拷贝出来 https://vuepress.mirror.docker-practice.com/image/multistage-builds/#分散到多个-dockerfile docker build -t go/helloworld:build . -f Dockerfile.build docker create --name extract go/helloworld:build docker cp extract:/go/src/github.com/go/helloworld/app ./app docker rm -f extract ~~~ ~~~ 还有一种办法, 前端 入口 sh 就是 将 容器内静态文件 挂载到 卷,并将 配置也挂载到卷,然后 向 nginx 容器 发送重载信号就行了 行不通,因为 容器的入口程序在容器内,无法操作宿主的其它容器。 ~~~ ---- ### 备份和恢复容器数据 **备份 MongoDB 数据演示** 1. 容器数据保存到 mongo-data 数据卷 中 ```shell $: docker run -p 27018:27017 --name mongo -v mongo-data:/data -d mongo:4.4 ``` 2. 用另一个 Ubuntu 容器来将数据备份出来 a. 运行一个 Ubuntu 的容器,挂载 要备份的容器的所有 volume b. 映射宿主机的 backup 目录到容器里面的 /backup 目录 c. 将卷中要备份的数据 压缩打包 ```shell $: docker run --rm --volumes-from mongo -v d:/backup:/backup ubuntu tar cvf /backup/backup.tar /data/ ``` 最后你就可以拿着这个 backup.tar 文件去其他地方导入了。 **恢复 Volume 数据演示** 运行一个 ubuntu 容器,挂载 要备份的 容器的所有 volumes,然后读取 /backup 目录中的备份文件,解压到 /data/ 目录 ```shell $: docker run --rm --volumes-from mongo -v d:/backup:/backup ubuntu bash -c "cd /data/ && tar xvf /backup/backup.tar --strip 1" ``` https://docker.easydoc.net/doc/81170005/cCewZWoN/XQEqNjiu 其实备份容器数据不用这么麻烦,直接进入容器打包,或者 在宿主机上操作备份 /var/lib/docker/volumes/mongo-data/_data 卷数据也可以。 ---- ### 阿里云私有 Docker Registry 仓库 [容器镜像服务 ACR - 阿里云](https://cr.console.aliyun.com/cn-hangzhou/instance/dashboard) ~~~ 公网: registry.cn-hangzhou.aliyuncs.com 内网: registry-internal.cn-hangzhou.aliyuncs.com 访问凭证 password: ****** ~~~ 使用方法: **登录** ```shell $: docker login registry.cn-hangzhou.aliyuncs.com --username=811800545@qq.com # 输入 访问凭证 # auths info: cat /root/.docker/config.json ``` **推送和拉取镜像** ```shell $: docker tag xiaobu191/php-api registry.cn-hangzhou.aliyuncs.com/yf5g/php-api $: docker push registry.cn-hangzhou.aliyuncs.com/yf5g/php-api $: docker pull registry.cn-hangzhou.aliyuncs.com/yf5g/php-api ``` 免费版本限额: - 全球地域下仓库默认限额为 300,请迁移至企业版获取更高配额 - 全球地域下命名空间默认限额为 3,请迁移企业版获取更高配额 - [什么是容器镜像服务ACR](https://help.aliyun.com/document_detail/257112.html?spm=5176.8351553.help.dexternal.421c1991CHGvZp) [阿里云Docker仓库_imonkeyi的博客-CSDN博客](https://blog.csdn.net/imonkeyi/article/details/120557675) ---- ### 相关资源 https://github.com/khs1994-docker/laravel-demo/blob/master/Dockerfile https://github.com/mlocati/docker-php-extension-installer **php-cli:** docker pull php:7.4.33-cli-alpine3.16 https://hub.docker.com/layers/library/php/7.4.33-cli-alpine3.16/images/sha256-1e1b3bb4ee1bcb039f559adb9a3fae391c87205ba239b619cdc239b78b7f2557?context=explore https://github.com/docker-library/php/blob/b9f17156020c3aef71df681b27684533529347a7/7.4/alpine3.16/cli/Dockerfile **php-fpm:** docker pull php:7.4.33-fpm-alpine3.16 https://hub.docker.com/layers/library/php/7.4.33-fpm-alpine3.16/images/sha256-4ca7fd8bea83cb12e43d192531576ca9a6b6a2d995763d3aaaee34f0d643f206?context=explore https://github.com/docker-library/php/blob/b9f17156020c3aef71df681b27684533529347a7/7.4/alpine3.16/fpm/Dockerfile **nginx:** docker pull nginx:1.25.0-alpine3.17 https://hub.docker.com/layers/library/nginx/1.25.0-alpine3.17/images/sha256-0b0af14a00ea0e4fd9b09e77d2b89b71b5c5a97f9aa073553f355415bc34ae33?context=explore https://github.com/nginxinc/docker-nginx/blob/3591b5e431af710432bd4852d9ee26eb19992776/mainline/alpine-slim/Dockerfile https://github.com/nginxinc/docker-nginx/blob/123ef33694fccfefcb7db63251b21c0496537c76/mainline/alpine/Dockerfile ----