## 笔记
[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
----
- 开始
- 公益
- 更好的使用看云
- 推荐书单
- 优秀资源整理
- 技术文章写作规范
- SublimeText - 编码利器
- PSR-0/PSR-4命名标准
- php的多进程实验分析
- 高级PHP
- 进程
- 信号
- 事件
- IO模型
- 同步、异步
- socket
- Swoole
- PHP扩展
- Composer
- easyswoole
- php多线程
- 守护程序
- 文件锁
- s-socket
- aphp
- 队列&并发
- 队列
- 讲个故事
- 如何最大效率的问题
- 访问式的web服务(一)
- 访问式的web服务(二)
- 请求
- 浏览器访问阻塞问题
- Swoole
- 你必须理解的计算机核心概念 - 码农翻身
- CPU阿甘 - 码农翻身
- 异步通知,那我要怎么通知你啊?
- 实时操作系统
- 深入实时 Linux
- Redis 实现队列
- redis与队列
- 定时-时钟-阻塞
- 计算机的生命
- 多进程/多线程
- 进程通信
- 拜占庭将军问题深入探讨
- JAVA CAS原理深度分析
- 队列的思考
- 走进并发的世界
- 锁
- 事务笔记
- 并发问题带来的后果
- 为什么说乐观锁是安全的
- 内存锁与内存事务 - 刘小兵2014
- 加锁还是不加锁,这是一个问题 - 码农翻身
- 编程世界的那把锁 - 码农翻身
- 如何保证万无一失
- 传统事务与柔性事务
- 大白话搞懂什么是同步/异步/阻塞/非阻塞
- redis实现锁
- 浅谈mysql事务
- PHP异常
- php错误
- 文件加载
- 路由与伪静态
- URL模式之分析
- 字符串处理
- 正则表达式
- 数组合并与+
- 文件上传
- 常用验证与过滤
- 记录
- 趣图
- foreach需要注意的问题
- Discuz!笔记
- 程序设计思维
- 抽象与具体
- 配置
- 关于如何学习的思考
- 编程思维
- 谈编程
- 如何安全的修改对象
- 临时
- 临时笔记
- 透过问题看本质
- 程序后门
- 边界检查
- session
- 安全
- 王垠
- 第三方数据接口
- 验证码问题
- 还是少不了虚拟机
- 程序员如何谈恋爱
- 程序员为什么要一直改BUG,为什么不能一次性把代码写好?
- 碎碎念
- 算法
- 实用代码
- 相对私密与绝对私密
- 学习目标
- 随记
- 编程小知识
- foo
- 落盘
- URL编码的思考
- 字符编码
- Elasticsearch
- TCP-IP协议
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依赖注入
- 开发笔记
- 经纬度格式转换
- php时区问题
- 解决本地开发时调用远程AIP跨域问题
- 后期静态绑定
- 谈tp的跳转提示页面
- 无限分类问题
- 生成微缩图
- MVC名词
- MVC架构
- 也许模块不是唯一的答案
- 哈希算法
- 开发后台
- 软件设计架构
- mysql表字段设计
- 上传表如何设计
- 二开心得
- awesomes-tables
- 安全的代码部署
- 微信开发笔记
- 账户授权相关
- 小程序获取是否关注其公众号
- 支付相关
- 提交订单
- 微信支付笔记
- 支付接口笔记
- 支付中心开发
- 下单与支付
- 支付流程设计
- 订单与支付设计
- 敏感操作验证
- 排序设计
- 代码的运行环境
- 搜索关键字的显示处理
- 接口异步更新ip信息
- 图片处理
- 项目搭建
- 阅读文档的新方式
- mysql_insert_id并发问题思考
- 行锁注意事项
- 细节注意
- 如何处理用户的输入
- 不可见的字符
- 抽奖
- 时间处理
- 应用开发实战
- python 学习记录
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文档相似度验证
- thinkphp5.0数据库与模型的研究
- workerman进程管理
- workerman网络分析
- java学习记录
- docker
- 笔记
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京东
- pc_detailpage_wareBusiness
- doc
- 电商网站设计
- iwebshop
- 商品规格分析
- 商品属性分析
- tpshop
- 商品规格分析
- 商品属性分析
- 电商表设计
- 设计记录
- 优惠券
- 生成唯一订单号
- 购物车技术
- 分类与类型
- 微信登录与绑定
- 京东到家库存系统架构设计
- crmeb
- 命名规范
- Nginx https配置
- 关于人工智能
- 从人的思考方式到二叉树
- 架构
- 今日有感
- 文章保存
- 安全背后: 浏览器是如何校验证书的
- 避不开的分布式事务
- devops自动化运维、部署、测试的最后一公里 —— ApiFox 云时代的接口管理工具
- 找到自己今生要做的事
- 自动化生活
- 开源与浆果
- Apifox: API 接口自动化测试指南