## 筆記
[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 接口自動化測試指南