---
tags: SRE_靜宜(PU)
---
# Docker Image
<!--15-Docker-Image-->
<!---->
## 認識 Dockerfile
- Dockerfile 就是建置 Docker Image 的腳本

<!--1. Best practices for writing Dockerfiles
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
2. Top 20 Docker Security Tips (一定要看)
https://towardsdatascience.com/top-20-docker-security-tips-81c41dd06f57 -->
# 自製 Alpine OpenSSH Server 的 Docker Image
## 撰寫 alpine.plus Dockerfile
$ cd ~/wulin; mkdir plus
$ nano plus/Dockerfile
FROM alpine.base
RUN apk update && \
apk add --no-cache openssh-server tzdata && \
# 設定時區
cp /usr/share/zoneinfo/Asia/Taipei /etc/localtime && \
ssh-keygen -t rsa -P "" -f /etc/ssh/ssh_host_rsa_key && \
echo -e 'Welcome to Alpine 3.11.6\n' > /etc/motd && \
# 建立管理者帳號 bigred
adduser -s /bin/bash -h /home/bigred -G wheel -D bigred && echo '%wheel ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers && \
echo -e "bigred\nbigred\n" | passwd bigred &>/dev/null && [ "$?" == "0" ] && echo "bigred ok"
EXPOSE 22
ENTRYPOINT ["/usr/sbin/sshd"]
CMD ["-D"]
[重要] ENTRYPOINT 所指定的執行命令, 在建立 Container 時強制執行此命令並且不可被其它命令取代
<!--The CMD instruction has three forms:
1. CMD ["executable","param1","param2"] (exec form, this is the preferred form)
這種格式直接執行 CMD 中的系統命令, 它是使用 JSON 陣列格式, 來描述系統命令
2. CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
這種格式必需搭配 ENTRYPOINT 命令
3. CMD command param1 param2 (shell form)
這種格式必定使用 sh -c 命令執行 RUN 後面的系統命令
There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.
-->
## 建立與使用 alpine.plus image
$ docker build --no-cache -t alpine.plus plus/
$ docker run --rm --name s1 -h s1 alpine.plus sh
Extra argument sh.
[重要] 因使用 entrypoint 宣告內定命令, 便無法自行指定執行命令
## 建立 s1 container
$ docker run --rm --name s1 -h s1 -d -p 22100:22 alpine.plus
登入 s1 貨櫃主機
$ ssh puxxxx@localhost -p 22100
puxxxx@140.128.xx.xx's password: xxxxx
$ ps aux
........
$ sudo kill -9 1
sudo: setrlimit(RLIMIT_CORE): Operation not permitted
$ exit
$ docker stop s1
---
$ docker ps -a
$ docker rm -f s1
$ network rm mynet
$ network create mynetxx
$ nano *network*
(8 9 改成自己要的不能和別人重疊)
$ docker run --rm --name s2021 -net=mynetxx -d -p 22100:22 astich/alpine.plus
$ docker exec -it s2021 sh
/# ifconfig
不能用 bigred 登入, 令建立帳號個別, 就無須 sudo 權限
$ docker exec s01 useradd -m -s /bin/bash s01
$ docker exec -it s01 sh
/# passwd s01
(不用一直做 images)
<!--1. Introducing dumb-init, an init system for Docker containers (一定要看: 詳細解釋如何執行 Container 中的第一個程式)
https://engineeringblog.yelp.com/2016/01/dumb-init-an-init-for-docker.html
2. The PID 1 problem: reaping zombies (zombies 解釋很清楚)
https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/
3. Docker demons: PID-1, orphans, zombies, and signals.
https://www.fpcomplete.com/blog/2016/10/docker-demons-pid1-orphans-zombies-signals
$ nano plus/Dockerfile
FROM alpine.base
RUN apk update && apk upgrade && apk add --no-cache openssh openrc && rc-update add sshd && \
# 設定 OpenSSH
mkdir /run/openrc && touch /run/openrc/softlevel && rc-status &>/dev/null && \
echo -e 'Welcome to Alpine 3.11.6\n' > /etc/motd && \
# 建立管理者帳號 bigred
adduser -s /bin/bash -h /home/bigred -G wheel -D bigred && echo '%wheel ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers && \
echo -e "bigred\nbigred\n" | passwd bigred &>/dev/null && [ "$?" == "0" ] && echo "bigred ok"
CMD ["/bin/bash", "-c","/etc/init.d/sshd start && ps aux && tail -f /dev/null"]
$ docker rmi alpine.plus; docker build --no-cache -t alpine.plus plus/
s1 container 建立流程如下:
/bin/bash -c 這命令因是第一個執行 ,會先佔有 PID 1, 然後執行 etc/init.d/sshd start 這命令, 如下它的 PID 是 29, 最後再執行 tail -f /dev/null,
$ docker logs s1
..............
/lib/rc/sh/openrc-run.sh: line 100: can't create /sys/fs/cgroup/systemd/tasks: Read-only file system
ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519
* Starting sshd ... [ ok ]
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 17.0 0.0 2192 1656 ? Ss 06:59 0:00 /bin/bash -c /etc/init.d/sshd start && ps aux && tail -f /dev/null
root 29 0.0 0.0 4316 536 ? S 06:59 0:00 /usr/sbin/sshd
root 35 0.0 0.0 1616 556 ? R 06:59 0:00 ps aux
因最後這 Conatiner 是這個 tail -f /dev/null 命令在前景執行, 這時它會取代 /bin/bash -c 命令, PID 會變 1
$ docker exec s1 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1936 564 ? Ss 07:06 0:00 tail -f /dev/null
root 29 0.0 0.0 4316 544 ? S 07:06 0:00 /usr/sbin/sshd
root 37 0.0 0.0 1616 564 ? Rs 07:25 0:00 ps aux
使用 ssh 登入 s1 Conatiner
$ docker exec s1 hostname -i
172.17.0.2
$ ssh 172.17.0.2
bigred@172.17.0.2's password:
Welcome to Alpine 3.10.2
s1:~$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1936 564 ? Ss 07:06 0:00 tail -f /dev/null
root 29 0.0 0.0 4316 544 ? S 07:06 0:00 /usr/sbin/sshd
root 48 0.0 0.0 4348 3468 ? Ss 07:29 0:00 sshd: bigred [priv]
bigred 50 0.0 0.0 4348 2440 ? S 07:29 0:00 sshd: bigred@pts/0
bigred 51 0.0 0.0 2400 2032 pts/0 Ss 07:29 0:00 -bash
bigred 57 0.0 0.0 1616 556 pts/0 R+ 07:29 0:00 ps aux
s1:~$ pstree -p
tail(1)---sshd(29)---sshd(48)---sshd(50)---bash(51)---pstree(58)
s1:~$ exit
logout
再次檢視 Process ID
$ docker exec s1 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1936 564 ? Ss 07:06 0:00 tail -f /dev/null
root 29 0.0 0.0 4316 544 ? S 07:06 0:00 /usr/sbin/sshd
bigred 50 0.0 0.0 0 0 ? Z 07:29 0:00 [sshd] <defunct>
root 59 0.0 0.0 1616 508 ? Rs 07:34 0:00 ps aux
$ docker exec s1 pstree -p
tail(1)-+-sshd(29)
`-sshd(50)
以上資訊顯示, PID 50 竟然變成 zombit, 這是因爲 tail -f /dev/null 不具有 reaping orphaned zombie processes 功能,
如再次使用 ssh 登入 s1 container, 這時又會多一 zombit, 如下 :
$ ssh 172.17.0.2
bigred@172.17.0.2's password:
Welcome to Alpine 3.10.2
s1:~$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1936 564 ? Ss 07:06 0:00 tail -f /dev/null
root 29 0.0 0.0 4316 544 ? S 07:06 0:00 /usr/sbin/sshd
bigred 50 0.0 0.0 0 0 ? Z 07:29 0:00 [sshd] <defunct>
root 70 0.1 0.0 4348 3492 ? Ss 07:44 0:00 sshd: bigred [priv]
bigred 72 0.0 0.0 4348 2344 ? S 07:44 0:00 sshd: bigred@pts/0
bigred 73 0.0 0.0 2404 2080 pts/0 Ss 07:44 0:00 -bash
bigred 74 0.0 0.0 1616 560 pts/0 R+ 07:44 0:00 ps aux
s1:~$ exit
logout
$ docker exec s1 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1936 564 ? Ss 07:06 0:00 tail -f /dev/null
root 29 0.0 0.0 4316 1964 ? S 07:06 0:00 /usr/sbin/sshd
bigred 50 0.0 0.0 0 0 ? Z 07:29 0:00 [sshd] <defunct>
bigred 72 0.0 0.0 0 0 ? Z 07:44 0:00 [sshd] <defunct>
root 80 0.0 0.0 1616 572 ? Rs 07:45 0:00 ps aux
$ docker exec s1 pstree -p
tail(1)-+-sshd(29)
|-sshd(50)
`-sshd(72)
要解決以上問題, 可用 dump-init 來解決
$ docker run --init --name s1 -h s1 -d alpine.plus
c71b81b1654597b355e763b1d9e1b12c0067d0e5a068dcb10091a84f3e3206fe
$ docker logs s1
........
/lib/rc/sh/openrc-run.sh: line 100: can't create /sys/fs/cgroup/systemd/tasks: Read-only file system
ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519
* Starting sshd ... [ ok ]
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 8.5 0.0 1084 4 ? Ss 15:24 0:00 /sbin/docker-init -- /bin/bash -c /etc/init.d/sshd start && ps aux && tail -f /dev/null
root 6 0.0 0.0 2192 1604 ? S 15:24 0:00 /bin/bash -c /etc/init.d/sshd start && ps aux && tail -f /dev/null
root 28 0.0 0.0 4312 540 ? S 15:24 0:00 /usr/sbin/sshd
root 35 0.0 0.0 1616 456 ? R 15:24 0:00 ps aux
$ docker exec s1 ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.3 0.0 1084 4 ? Ss 15:24 0:00 /sbin/docker-init -- /bin/bash -c /etc/init.d/sshd start && ps aux && tail -f /dev/null
root 6 0.0 0.0 1936 464 ? S 15:24 0:00 tail -f /dev/null
root 28 0.0 0.0 4312 540 ? S 15:24 0:00 /usr/sbin/sshd
root 36 0.0 0.0 1616 548 ? Rs 15:24 0:00 ps aux
$ docker exec s1 pstree -p
docker-init(1)-+-sshd(28)
`-tail(6)
再次登入 s1 Container
$ rm ~/.ssh/known_hosts
$ ssh 172.17.0.2
bigred@172.17.0.2's password:
Welcome to Alpine 3.10.2
s1:~$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1084 4 ? Ss 15:24 0:00 /sbin/docker-init -- /bin/bash -c /etc/init.d/s
root 6 0.0 0.0 1936 464 ? S 15:24 0:00 tail -f /dev/null
root 28 0.0 0.0 4312 540 ? S 15:24 0:00 /usr/sbin/sshd
root 48 0.1 0.0 4348 3500 ? Ss 15:28 0:00 sshd: bigred [priv]
bigred 50 0.0 0.0 4348 2568 ? S 15:28 0:00 sshd: bigred@pts/0
bigred 51 0.0 0.0 2400 2068 pts/0 Ss 15:28 0:00 -bash
bigred 52 0.0 0.0 1616 452 pts/0 R+ 15:29 0:00 ps aux
s1:~$ pstree -p
docker-init(1)-+-sshd(28)---sshd(48)---sshd(50)---bash(51)---pstree(53)
`-tail(6)
s1:~$ exit
logout
$ docker exec s1 pstree -p
docker-init(1)-+-sshd(28)
`-tail(6)
以上操作不會出現 zombit process, 但這時可使用 sudo kill -9 將 tail -f /dev/null 命令關閉, 這時會造成 s1 Container 停止運作, 如下 :
$ ssh 172.17.0.2
bigred@172.17.0.2's password:
Welcome to Alpine 3.10.2
s1:~$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 1084 4 ? Ss 15:24 0:00 /sbin/docker-init -- /bin/bash -c /etc/init.d/s
root 6 0.0 0.0 1936 464 ? S 15:24 0:00 tail -f /dev/null
root 28 0.0 0.0 4312 2008 ? S 15:24 0:00 /usr/sbin/sshd
root 66 0.0 0.0 4348 3488 ? Ss 15:32 0:00 sshd: bigred [priv]
bigred 68 0.0 0.0 4348 2520 ? S 15:32 0:00 sshd: bigred@pts/0
bigred 69 0.0 0.0 2404 2180 pts/0 Ss 15:32 0:00 -bash
bigred 70 0.0 0.0 1616 460 pts/0 R+ 15:32 0:00 ps aux
s1:~$ sudo kill -9 6
Connection to 172.17.0.2 closed by remote host.
$ docker exec -it s1 bash
Error response from daemon: Container c71b81b1654597b355e763b1d9e1b12c0067d0e5a068dcb10091a84f3e3206fe is not running -->
# Docker Image 備份與還原
<!--1. [Import images to k3s without docker registry](https://cwienczek.com/2020/06/import-images-to-k3s-without-docker-registry/)
-->
## Docker Image 備份
$ cd ~/wk/wulin
$ docker history alpine.plus
- 備份 Image
$ docker save alpine.plus > alpine.plus.tar
<!--下載 Docker 輔助命令
$ docker run --rm -v /home/bigred/bin:/opt dafu/dkutil:16.04 download.sh
$ sudo chown -R bigred:bigred ~/bin/
$ docker rmi dafu/dkutil:16.04
$ sudo reboot
-->
## Docker Image 還原
- 還原 Image
$ docker rmi alpine.plus
$ docker load < alpine.plus.tar
$ docker history alpine.plus
[重點] 還原的 image 的目錄架構, 與原先 Image目錄架構一樣
## Docker Container 備份與還原
$ docker run --name s2 -h s2 -d alpine.plus
6e52bd5aee18c0aed8dbb17920037be0b9109158329b401c56b4f64417c2d784
[重要] 使用上述命令建立 s2 Container, 這個 Container 有啟動 openssh server, 所以這個 Container 有 openssh 的執行狀態資訊檔, 這時使用 docker export 命令
匯出的 Tar 檔中就會有殘留 openssh 的執行暫存檔, 以至後續做出的 image 無法啟動 openssh server, 所以必須先關閉 s2 container, 才可備份此 container
$ docker stop s2
$ docker export s2 > s2.tar
$ docker rm s2 && docker rmi alpine.plus
$ cat s2.tar | docker import - alpine.plus &>/dev/null
$ docker history alpine.plus
<!--$ nano ~/bin/dkcompact
#!/bin/bash
[ "$1" == "" ] && echo "dkcompact image tag" && exit 1
if [ "$2" != "" ]; then
img="$1:$2"
else
img="$1"
fi
docker images | grep -e "^$1 *$2" &>/dev/null
if [ "$?" == "0" ]; then
docker run --name tempcontainer -itd $img /bin/bash &>/dev/null
docker export tempcontainer > /tmp/tempcontainer.tar
docker rm -f tempcontainer &>/dev/null
docker rmi $img &>/dev/null
cat /tmp/tempcontainer.tar | docker import - $img &>/dev/null
[ "$?" == "0" ] && echo "$img compact success"
else
echo "$img not exist"
fi
$ chmod +x ~/bin/dkcompact
-->
## 使用重製 alpine.plus image
$ docker run --name s2 -h s2 -d alpine.plus
docker: Error response from daemon: No command specified.
See 'docker run --help'.
[重要] 重製後的 alpine.plus image 必須指定 執行命令
$ docker run --name s2 -h s2 -d alpine.plus /usr/sbin/sshd -D
$ docker ps -a
$ docker rm -f s2