# Docker&Podman
[TOC]

* 把一個process跟他的相依檔利用隔離技術把他隔離出來。
* 一台實體電腦可以產生很多container,並且container會共用os的kernel。
> Application Container就是一台電腦,他有開機程序嗎?
> 答:沒有,因為他共用作業系統的kernel,主機的kernel已經啟動完畢。
## Chroot
```
$ mkdir -p ~/r2d2/rootfs/{bin,sbin,usr/{bin,sbin},dev,lib,proc}; cd ~/r2d2 ##建立目錄
$ tree rootfs
rootfs
├── bin
├── lib
└── proc
複製 sh 命令檔及相依程式庫
$ which sh
/bin/sh
$ ldd /bin/sh ##看sh相依檔
/lib/ld-musl-x86_64.so.1 (0x7fb68577b000)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7fb68577b000)
$ cp /bin/sh rootfs/bin; cp /lib/ld-musl-x86_64.so.1 rootfs/lib/
```
```
$ sudo chown -R root:root rootfs ##把rootfs裡面的所有資料owner都改成root,因為chroot這個命是是要root權限
$ sudo chroot rootfs /bin/sh
@alp:/$ echo $USER
root
@alp:/$ ls
/bin/sh: ls: not found
@alp:/$ exit
$ sudo chroot rootfs /bin/bash ##執行這段命令會失敗是因為rootfs裡面沒有這個program跟他的相依檔
chroot: failed to run command ‘/bin/bash’: No such file or directory
```

* chroot 會到 rootfs 目錄裡面執行 /bin/sh ,此時 program 會載入記憶體會變成 process,並且把 rootfs 這個目錄切換 /bin/sh 這個 process 的根目錄
```
$ sudo cp /bin/busybox rootfs/bin/
$ sudo chroot rootfs /bin/sh
@alp:/$ /bin/busybox --install -s ##裡面安裝busybox
@alp:/$ ls -alh /bin
total 1648
drwxr-sr-x 2 0 0 4096 Jun 24 13:24 .
drwxr-sr-x 7 0 0 4096 Jun 24 13:23 ..
lrwxrwxrwx 1 0 0 12 Jun 24 13:24 arch -> /bin/busybox
lrwxrwxrwx 1 0 0 12 Jun 24 13:24 ash -> /bin/busybox
........
@alp:/$ ls -al /proc ##/PROC這個目錄裡面沒東西
total 8
drwxr-sr-x 2 0 0 4096 Jun 24 13:03 .
drwxr-sr-x 7 0 0 4096 Jun 24 13:23 ..
@alp:/$ ps ##ps這個命令一定會去讀proc目錄的process資訊,並且顯示在我們的螢幕。
PID USER TIME COMMAND
@alp:/$ exit
```
## Overlay2

他是一個立體結構的檔案系統,upper漂浮在lower之上,又稱為堆疊。
merged是一個介面,所有操作都會在這層目錄上,但真正在執行的目錄是upper。
upper write and read。
lower read only。
```
$ cd ~;mkdir -p ov2/{upper,lower,merged,work}; cd ov2
$ echo "I'm from lower!" > lower/in_lower.txt
$ echo "I'm from upper!" > upper/in_upper.txt
$ echo "I'm from lower!" > lower/in_both.txt
$ echo "I'm from upper!" > upper/in_both.txt
```
啟動overlay2檔案系統,掛載到merged這個目錄
```
$ sudo mount -t overlay myov -o lowerdir=/home/bigred/ov2/lower,upperdir=/home/bigred/ov2/upper,workdir=/home/bigred/ov2/work /home/bigred/ov2/merged ##-t type是要掛載什麼檔案系統到merged目錄;-o是詳細資訊 這邊overlay2裡面的檔案目錄是哪些;work這個目錄是overlay2檔案系統自己要運作儲存資料的地方
* myov 是 mount 的名稱, 可由 執行 mount 得知
$ ls -al merged
total 20
drwxr-sr-x 1 bigred bigred 4096 Jul 23 05:00 .
drwxr-sr-x 6 bigred bigred 4096 Jul 23 04:58 ..
-rw-r--r-- 1 bigred bigred 16 Jul 23 05:00 in_both.txt
-rw-r--r-- 1 bigred bigred 16 Jul 23 05:00 in_lower.txt
-rw-r--r-- 1 bigred bigred 16 Jul 23 05:00 in_upper.txt
bigred@alp:~/ov2$ tree ##掛在到merged目錄in_both.txt是upper目錄區來的
.
├── lower
│ ├── in_both.txt
│ └── in_lower.txt
├── merged
│ ├── in_both.txt
│ ├── in_lower.txt
│ └── in_upper.txt
├── upper
│ ├── in_both.txt
│ └── in_upper.txt
└── work
└── work [error opening dir]
$ cat merged/in_both.txt
I'm from upper!
$ echo 'new file' > merged/new_file
$ ls -l */new_file
-rw-r--r-- 1 bigred bigred 9 Apr 29 23:16 merged/new_file
-rw-r--r-- 1 bigred bigred 9 Apr 29 23:16 upper/new_file
```
```
# whiteout 操作範例
$ rm merged/in_both.txt ##這個動作其實是刪除upper目錄裡面的in_both.txt。
$ ls -al merged/
........
-rw-r--r-- 1 bigred bigred 16 Apr 29 23:12 in_lower.txt
-rw-r--r-- 1 bigred bigred 16 Apr 29 23:13 in_upper.txt
-rw-r--r-- 1 bigred bigred 9 Apr 29 23:16 new_file
$ ls -al upper/ | grep in_both.txt
c--------- 2 root root 0, 0 Jul 25 09:50 in_both.txt ##刪除但是資料還在,其實是看不到的,這個檔案已經變成字元,雖然lower有這個檔案,但是被一個隱形的檔案給擋住,所以從上面看會看不到這個檔案。但如果只有upper檔案,但lower沒有檔案,那刪除檔案時再upper這層目錄就會真的直接刪除。
$ echo "back" > merged/in_both.txt
$ ls -al upper/ | grep in_both.txt
-rw-r--r-- 1 bigred bigred 5 Jul 25 09:59 in_both.txt
```
### copy_up 操作範例
如果要修改in_lower.txt檔案但是upper沒有這個檔案,那就會把lower的in_lower.txt檔案複製到upper做修改
```
$ ls -al upper
total 20
drwxr-sr-x 2 bigred bigred 4096 Jul 25 09:59 .
drwxr-sr-x 6 bigred bigred 4096 Jul 23 04:58 ..
-rw-r--r-- 1 bigred bigred 5 Jul 25 09:59 in_both.txt
-rw-r--r-- 1 bigred bigred 16 Jul 23 05:00 in_upper.txt
-rw-r--r-- 1 bigred bigred 9 Jul 23 05:05 new_file
$ cat merged/in_lower.txt
I'm from lower!
$ ls -al upper | grep in_lower.txt
$ echo "go or no go" >> merged/in_lower.txt
$ cat upper/in_lower.txt
I'm from lower!
go or no go
$ sudo umount merged
```
by pass overlay2:他可以跳過overlay2檔案系統,直接到主機的檔案系統做使用。
因為overlay2不適合用於高頻率的資料刷新,例如:mysql,mssql
## CGroup

他可以限制container的記憶體,cpu,網路的資源使用量。
tasks是要放限制對像的pid。
CGROUP:是用MKDIR 創造demo這層目錄,把memory這個目錄裡的所有內容都放在demo裡面
```
$ ls /sys/fs/cgroup/
blkio cpuacct devices hugetlb net_cls openrc pids
cpu cpuset freezer memory net_prio perf_event unified
$ sudo mkdir /sys/fs/cgroup/memory/demo
$ ls /sys/fs/cgroup/memory/demo/
cgroup.clone_children memory.kmem.tcp.max_usage_in_bytes memory.oom_control
cgroup.event_control memory.kmem.tcp.usage_in_bytes memory.pressure_level
cgroup.procs memory.kmem.usage_in_bytes memory.soft_limit_in_bytes
memory.failcnt memory.limit_in_bytes
........
```
```
$ nano memlimit.sh ##沒有demo目錄就幫你產生
#!/bin/bash
[ ! -d /sys/fs/cgroup/memory/demo ] && sudo mkdir /sys/fs/cgroup/memory/demo
echo "100000000" | sudo tee /sys/fs/cgroup/memory/demo/memory.limit_in_bytes &>/dev/null
echo $$ | sudo tee /sys/fs/cgroup/memory/demo/tasks &>/dev/null
cat /dev/zero | head -c $1 | tail ##零號裝置會一直噴出null字元,head -c 可以控制我們要多少的量但他會顯示在螢幕,所以需要tail他會儲存在記憶體,就會造成霸佔記憶體。
* 上面程式 除了 memlimit.sh 的 PID 存入 /sys/fs/cgroup/memory/demo/tasks, 還會將 memlimit.sh 的 child PID 存入 tasks
$ chmod +x memlimit.sh
$ ./memlimit.sh 82m
$ ./memlimit.sh 100m
./memlimit.sh: line 9: 5374 Broken pipe cat /dev/zero
5375 | head -c $1
5376 Killed | tail
```
## Linux Namespace
- Mount (mnt)
- Process ID (pid)
- Network (net)
- Interprocess Communication (ipc)
- UTS (UNIX Time-Sharing)
- User ID (user)
- Control group (cgroup) Namespace
- Time Namespace
```
$ ls -l /proc/self/ns/ ##namespace的隔離資訊都會丟到這個目錄裡面
total 0
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 net -> 'net:[4026532000]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 time -> 'time:[4026531834]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 user -> 'user:[4026531837]'
lrwxrwxrwx 1 bigred bigred 0 Apr 6 14:39 uts -> 'uts:[4026531838]'
```
namespace命令
```
$ sudo unshare --uts sh ## --uts電腦名稱,把sh這個process有自己的電腦名稱
root@alp:/home/bigred$ ls -l /proc/self/ns/ | grep uts
lrwxrwxrwx 1 root root 0 Apr 6 14:47 uts -> uts:[4026532552]
[註] 與 ALP 虛擬主機的 uts Namespace ID (前一張投影片) 不一樣, 代表 sh 這個 Process 有啟動 uts namespace
root@alp:/home/bigred$ hostname ##是root身份是因為我們下sudo
ALP
root@alp:/home/bigred$ hostname ssn763
root@ssn763:/home/bigred$ hostname
ssn763
root@ssn763:/home/bigred$ exit
$ hostname
alp
```

讓自己的process擁有自己的process system
```
$ sudo unshare --pid --fork sh ##讓sh這個process可以有獨立的記憶體處存空間,還有他的子程式。--fork可以在sh process裡面再產生一個process,child的概念;--pid是替sh process,產生一個他專屬process的記憶體空間,可以記錄他跟他小孩的資訊
$ echo $$
1
$ ps
PID TTY TIME CMD
1237 pts/0 00:00:00 unshare
1238 pts/0 00:00:00 sh
1241 pts/0 00:00:00 ps
# exit
```
* 雖然啟用 PID Namespace, ps 命令還是會看到 Host (alp) 的所有 Process 資訊, 這是因爲 ps 命令會讀取 ALP 的 /proc 目錄資訊
* 掛載專屬於sh的/proc目錄,這樣在打ps命令時就只會讀取到sh process底下的所有process資訊
```
$ sudo unshare --pid --fork --mount-proc sh
# ps
PID USER TIME COMMAND
1 root 0:00 sh
2 root 0:00 ps
# sleep 60 &
# pstree -p
sh(1)─┬─pstree(4)
└─sleep(3)
--mount-proc 這參數, 會將 sh 的 PID Namespace, 掛載到 ALP 的 /proc 目錄, 由以下 mount 命令, 得知 /proc 目錄被掛載二次
# findmnt proc
TARGET SOURCE FSTYPE OPTIONS
/proc proc proc rw,nosuid,nodev,noexec,relatime ##上面這個/proc是sh掛載的
/proc proc proc rw,nosuid,nodev,noexec,relatime ##下面這個/proc是原本alp主機的process資訊
執行以下命令, 會自動卸載 /proc 目錄的第二次掛載
# exit
```
## User Namespace
* conatiner 內部可以擁有自己的 user ,不一定要是root(rootless)。
```
需使用 sudo 執行以下命令, 才會成功
$ unshare --pid --fork --mount-proc --uts -R c3po/rootfs sh
unshare: unshare failed: Operation not permitted
沒使用 sudo 執行 unshare 命令, 必需要有 --user 參數才可執行, 以下命令 container 內定使用者為 nobody (id:65534), 執行 unshare 命令, 則是 bigred 帳
號
$ unshare --user whoami
nobody
## -c --map-current-use 他會使用目前alpine的使用者作登入
$ unshare --user -c --pid --fork --mount-proc --uts -R ~/c3po/rootfs sh
@alp:// $ ls -ald /
drwxr-sr-x 20 1000 1000 4096 Aug 15 16:00 /
@alp:// $ exit
$ unshare --user -r --pid --fork --mount-proc --uts -R ~/c3po/rootfs sh
root@alp:// $ ls -ald /
drwxr-sr-x 20 root root 4096 Aug 15 16:00 /
root@alp:// $ exit
```
> 問題:為什麼 unshare 前面要加sudo?
> 答:因為root才可以創造namespace
## Linux Network Namespace
Network Namespace:網路的隔離空間

左-實體主機 右-隔離主機
* 所有的linux作業系統一定都會產生一個network namespace叫做default
* 所有網路卡(eth0,lo,ve1),網路設備都一定會被塞到default network namespace
* ifconfig會看到network namespace所在執行的所有網路卡資訊。
> 問題:請問default network namespace他的隔離對象是誰?
> 答:/sbin/init
```
$ sudo unshare --pid --fork --mount-proc --net --uts sh ##-net創造網路的namespace
root@alp:/home/bigred$ ip a ##ip這個命令可以創造network namespace
1: 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 ##lo這個網路卡是自我檢查用的
回到原先 命令提示字元 視窗, 執行以下命令
$ sudo lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026532000 net 115 1 root unassigned /sbin/init ##alp主機的namespace
4026532555 net 2 4582 root unassigned unshare --pid --fork --mount-proc - ##sh process的namespace
$ sudo ip link add ve1 netns 1 type veth peer name ve2 netns 4582 ##netns就是network namespace;把ve1的網路卡連接到netns pid是1的身上;link就是連接網路線
$ sudo ip link set ve1 up ##啟動網卡
$ sudo ip addr add 192.168.1.100/24 dev ve1 ##給ve1這張網卡ip
```
> 問題:我們自己產生network namespace的虛擬網路線,還有網路卡什麼時候斷?
> 答:他是存在記憶體裡面,所以只有記憶體斷線他才會斷線,他也比一般的網路線損毀率還要低。
```
$ ip a
1: 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
2: ve2@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether ca:cb:7c:68:13:83 brd ff:ff:ff:ff:ff:ff link-netnsid 0
##啟動container 網卡
$ ip link set ve2 up
##給網卡ip
$ ip addr add 192.168.1.200/24 dev ve2
$ ping -c 1 192.168.1.100
PING 192.168.1.100 (192.168.1.100) 56(84) bytes of data.
64 bytes from 192.168.1.100: icmp_seq=1 ttl=64 time=0.063 ms
.......
$ exit
```
## Namespace & Chroot
```
$ cd; dir r2d2/rootfs/proc
total 8.0K
drwxr-sr-x 2 root root 4.0K Apr 29 22:05 .
drwxr-sr-x 8 root root 4.0K Apr 29 22:05 ..
$ sudo unshare --pid --fork --mount-proc --uts -R r2d2/rootfs sh ##-R就是chroot
@alp:/$ whoami
whoami: unknown uid 0
@alp:/$ mount
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
@alp:/$ exit
$ ls -al /proc ##這邊看sh porcess裡面的proc資訊有資料,是因為--mount-proc把sh自己的process資訊掛載上來
total 4
dr-xr-xr-x 184 0 0 0 Jun 26 02:51 .
drwxr-sr-x 8 0 0 4096 Jun 25 05:13 ..
dr-xr-xr-x 9 0 0 0 Jun 26 02:51 1
dr-xr-xr-x 9 0 0 0 Jun 26 02:55 6
dr-xr-xr-x 3 0 0 0 Jun 26 02:55 acpi
-r--r--r-- 1 0 0 0 Jun 26 02:55 buddyinfo
[註] --mount-proc 這個參數, 會自動 執行 "mount -t proc /proc /proc" 這行命令
```
## Namespace & CGroup
```
$ cd ~/r2d2
$ sudo mkdir /sys/fs/cgroup/memory/demo
$ echo "100000000" | sudo tee /sys/fs/cgroup/memory/demo/memory.limit_in_bytes
$ ls -al /dev/zero ##零號裝置在以前是用來徹底清除資料用的,他可以消磁
crw-rw-rw- 1 root root 1, 5 Jun 16 12:02 /dev/zero
$ sudo mount --bind -o ro /dev rootfs/dev ##把零號裝置掛載到rootfs/dev目錄下;-ro:read only,他只能讀跟用,不能刪除這個裝置,
[註] 因 rootfs/dev 這目錄中, 不存在 zero 這裝置, 所以將 ALP 系統中的 /dev/ 目錄 掛載上來
```
```
$ sudo unshare --pid --fork --mount-proc --uts -R rootfs sh
root@alp:/$ </dev/zero head -c 200m | tail ##把資料導入到記憶體裡面
再啟動一個新的 CMD 視窗, 執行以下命令
$ ssh bigred@<alp.docker IP>
$ ps aux | grep " sh$"
root 6523 0.0 0.0 820 4 pts/0 S 22:35 0:00 unshare --pid --fork --mount-proc --uts -R rootfs sh
root 6524 0.0 0.0 1692 1052 pts/0 S+ 22:35 0:00 sh
$ echo "6524" | sudo tee /sys/fs/cgroup/memory/demo/tasks ##把要限制的pid丟到tasks檔案裡面
16727
```
回到 unshare 終端機,
```
root@alp:/$ </dev/zero head -c 200m | tail
Killed
[註] 因被設定 Memory Limit 為 100 M, 以至上述命令無法執行
$ exit
$ sudo umount rootfs/dev/
```
unshare要變成真的一台container需要一堆的設定,例如:就算隔離出了一個network namespace他還是需要做連線設定,才能真正的上網。
## oci組織
oci就是制定container的標準
1.container的設定檔
2.檔案系統的目錄區,裡面最重要的就是container的process跟他的相依檔
crun效能比runc好
```
$ [ -d rootfs/ ] && sudo rm -r rootfs/
$ mkdir rootfs; cd rootfs; curl -o alpine.tar.gz http://dl-cdn.alpinelinux.org/alpine/v3.14/releases/x86_64/alpine-minirootfs-3.14.2-x86_64.tar.gz; tar xvf alpine.tar.gz ;rm alpine.tar.gz; cd ..
$ dir rootfs/bin
..........
lrwxrwxrwx 1 bigred bigred 12 May 29 14:20 base64 -> /bin/busybox
lrwxrwxrwx 1 bigred bigred 12 May 29 14:20 bbconfig -> /bin/busybox
-rwxr-xr-x 1 bigred bigred 822K May 22 06:59 busybox
lrwxrwxrwx 1 bigred bigred 12 May 29 14:20 cat -> /bin/busybox
..........
[註] 在 /bin 目錄中的 命令, 大都是交由 busybox 命令執行
```
## runc
```
$ sudo apk update &>/dev/null ; sudo apk add runc
(1/1) Installing runc (1.0.0_rc93-r0)
Executing busybox-1.32.1-r6.trigger
OK: 880 MiB in 202 packages
因 runc 命令內定執行帳號為 root, 而 rootfs 目錄的 Owner 是 bigred, 所以需將 rootfs 目錄的 owner 改為 root
$ sudo chown root:root -R rootfs/
```
產生config.json設定檔
```
$ runc spec
$ nano config.json
{
"ociVersion": "1.0.1",
"process": {
"terminal": true,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
.....
"root": {
"path": "rootfs",
"readonly": false
},
},
"hostname": "bb8",
.....
```
用runc產生container
runc沒有網路功能
```
$ sudo runc run bb8
/ # hostname
bb8
/ # ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
8 root 0:00 ps aux
/ # ls -al /bin | head -n 5
total 816
drwxr-xr-x 2 1000 1000 4096 Jan 28 21:51 .
drwxr-xr-x 19 1000 1000 4096 Mar 1 15:19 ..
lrwxrwxrwx 1 1000 1000 12 Jan 28 21:51 arch -> /bin/busybox
lrwxrwxrwx 1 1000 1000 12 Jan 28 21:51 ash -> /bin/busybox
```
保護 Linux Kernel 系統目錄
```
/# mkdir /zzz
/# cat /proc/meminfo | head -n 3
MemTotal: 6088832 kB
MemFree: 5408756 kB
MemAvailable: 5738776 kB
Linux kernel 的目錄及檔案會受到保護
/ # rm /proc/meminfo
rm: remove '/proc/meminfo'? y
rm: can't remove '/proc/meminfo': Permission denied
關閉 bb8 Container
$ exit
```
runc有基本的管理container能力,(清單,刪除)但不完整
```
在 Windows 系統再開啟 CMD 視窗, 執行以下命令
> ssh bigred@<ALP.Docker IP>
$ sudo runc list
ID PID STATUS BUNDLE CREATED OWNER
bb10 4925 running /home/bigred 2021-08-05T09:54:28.892953564Z root
$ sudo runc delete bb10
ERRO[0000] cannot delete container bb10 that is not stopped: running
$ sudo runc delete -f bb10 ##強制刪除container
$ sudo runc list
```
### 問題
1. 請問透過 RunC 建立 Container 電腦, 需準備那些內容 ?
1.先安裝 runc
2.要先建立一個目錄,因為我們要在這個目錄底下執行sh這個program,把它變成process,給 chroot 使用;因為runc命令內定執行帳號為root,而rootfs目錄的Owner是bigred,所以需將rootfs目錄的owner改為root
3.在使用runc spec產生一個設定檔,之後我們就可以用我們改好的設定檔,使用runc產生出我們要的container。
2. 請問 bb8 Container 電腦系統中, 所啟動的 sh 程序, 在 Host (alp) 系統中, 是不是只是一個程序 (Process) ?
是一個程序,再alp打sudo runc list,可以看到我們剛剛產生的container的pid,就是一個process。
3. 請問 bb8 Container , 有開機程序嗎 ?
不用開機程序,因為產生container時,實體主機kernel已經準備好了,根本沒有開機
> oci組織的目標就是要來制定container的標準。
> container一定要有兩個的東西存在:
> 1.config.json檔案設定檔
> 2.rootfs/ 系統目錄區,這個目錄就是要放我們要啟動的process的程式的檔案
> container一定需要完整的linux作業系統嗎?
> container並不需要完整的實體主機的作業系統,使用什麼系統的檔案目錄結構不是重點(用什麼不是重點),container只需要一個process跟他所需要的相依檔去隔離的。但他還是會需要實體主機作業系統的kernel。
## containerd
* 他是一個開源專案,open source project
* containerd是cncf.io的其中一個畢業專案
* containerd主要的功能就是上網到docker公司去抓container image(目錄區跟設定檔)
* containerd也跟skopeo有相同的功能,它也可以上網抓image

containerd他在linux裡面他是一個daemon,代表在linux一啟動就會在背景執行,在微軟的系統裡稱為"服務"
1.ctr命令:對containerd下達指令叫他去產生container。
2.containerd會上網到docker公司去抓container image(他是一堆目錄)
3.image裡面有container要的目錄區跟設定檔
4.containerd 會unpack image變成bundle
5.之後交給runc去產生container
## 安裝與啟動 Containerd
```
$ sudo apk add containerd
(1/1) Installing containerd (1.5.4-r1)
Executing busybox-1.32.1-r6.trigger
OK: 1087 MiB in 215 packages
$ containerd -v
containerd github.com/containerd/containerd v1.6.6 10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1
$ sudo containerd & ##啟動Containerd
......
INFO[2021-05-04T20:27:58.882462798+08:00] containerd successfully booted in 0.039280s
INFO[2021-05-04T20:27:58.887494449+08:00] Start event monitor
INFO[2021-05-04T20:27:58.887720795+08:00] Start snapshots syncer
INFO[2021-05-04T20:27:58.887902496+08:00] Start cni network conf syncer
INFO[2021-05-04T20:27:58.888012074+08:00] Start streaming server
```
下載 Container Image
```
$ sudo ctr image pull quay.io/cloudwalker/busybox:latest ##quay.io有下載image的流量限制,同一個時間點大概10幾個
$ sudo ctr image ls -q
quay.io/cloudwalker/busybox:latest
$ sudo ctr run --tty quay.io/cloudwalker/busybox:latest b1 sh ##--tty虛擬終端機給給貝殼程式跑的
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
8 root 0:00 ps aux
/ # exit
$ sudo ctr container list
CONTAINER IMAGE RUNTIME
b1 quay.io/cloudwalker/busybox:latest io.containerd.runc.v2
$ sudo ctr container rm b1
$ sudo ctr image remove quay.io/cloudwalker/busybox:latest docker.io/library/busybox:latest
```
docker.io網站是放image的,它也有一個限制:六小時內同一個ip位置只能下載100次相同的image
## 研究 Docker image
### 認識與安裝 Skopeo(天蠍)
* Skopeo:他是負責研究container image的,他可以同時管理兩個image,一個是oci image,一個是docker image
* Skopeo:這個命令可以讓我們去下載中心去看image的資訊,我們再決定要不要下載下來。
```
在 Alpine Linux 執行以下命令
$ sudo apk add skopeo
$ skopeo -v
skopeo version 1.8.0 commit: b7fc5b608b641cd9b9aec2647f9236e31f8f3b27
```
由以下命令可得知 ozone image 有那些版本可下載
```
$ sudo skopeo list-tags docker://docker.io/apache/ozone
{
"Repository": "docker.io/apache/ozone",
"Tags": [
"0.3.0",
"0.4.0",
"0.4.1",
"0.5.0",
"1.0.0",
"1.1.0",
"1.2.0",
"1.2.1", ##目前最新版本
"latest"
]
}
$ sudo skopeo inspect docker://quay.io/cloudwalker/alpine ##看image裡面的細部資訊
{
"Name": "quay.io/cloudwalker/alpine",
........
"DockerVersion": "19.03.12",
"Labels": null,
....
}
```
下載並研究 Docker Image 內部結構
```
$ skopeo --insecure-policy copy docker://quay.io/cloudwalker/alpine.sshd dir:/tmp/alpine.sshd
Getting image source signatures
Copying blob 5843afab3874 done
Copying blob efde1a2cd25c done
Copying blob 8eb646da4db7 done
Copying config 47460458ea done
Writing manifest to image destination
Storing signatures
$ ls -alh /tmp/alpine.sshd
......
-rw-r--r-- 1 bigred bigred 3.3K Jun 25 22:57 47460458ea8df43635019e60d7c7edead32155d051143b0107c03279a3a3cee0
-rw-r--r-- 1 bigred bigred 2.7M Jun 25 22:56 5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b
-rw-r--r-- 1 bigred bigred 3.1M Jun 25 22:57 8eb646da4db772329c36c99c3edb14fce7cb3415c516104c4333d1325e908cc5
-rw-r--r-- 1 bigred bigred 17M Jun 25 22:57 efde1a2cd25c6076ffd0302bf7bfdd24c2b044772b122460fa9ad4cb32da088a
-rw-r--r-- 1 bigred bigred 951 Jun 25 22:57 manifest.json
-rw-r--r-- 1 bigred bigred 33 Jun 25 22:56 version
```
檢視 Docker Image Manifest 資訊
```
$ cat /tmp/alpine.sshd/manifest.json ##image的結構說明檔
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 3327,
"digest": "sha256:47460458ea8df43635019e60d7c7edead32155d051143b0107c03279a3a3cee0" ##做container的設定檔
},
"layers": [ ##這裡放三個壓縮檔,就是rootfs的檔案目錄,有三個目錄,指定給overlay2的lower目錄區,這三個目錄內容如果有相同的名稱,那後面的目錄會蓋掉前面的內容
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 2811478,
"digest": "sha256:5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 17282773,
"digest": "sha256:efde1a2cd25c6076ffd0302bf7bfdd24c2b044772b122460fa9ad4cb32da088a"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 3146213,
"digest": "sha256:8eb646da4db772329c36c99c3edb14fce7cb3415c516104c4333d1325e908cc5"
}
]
```
檢視 Docker Image Config 資訊
```
$ cat /tmp/alpine.sshd/47460458ea8df43635019e60d7c7edead32155d051143b0107c03279a3a3cee0 | jq ##這裡面的格式跟內容跟之前看得不太一樣,因為這是docker 的image
......
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"-D"
],
"Image": "sha256:6d140e79569c0ed96749138b1b836640a8932d7557d575a5d86cf876e0834df6",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": [
"/usr/sbin/sshd" ##這個就是要跑的process,並不是貝殼程式
],
"OnBuild": null,
},
"Labels": null
"Tty": false, ##不需要終端機
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
},
"created": "2021-09-05T07:53:46.062043091Z",
"docker_version": "20.10.7",
"history": [ ##Dockerfile
............
```
檢視 Docker Image Layer 資訊
```
$ cd /tmp/alpine.sshd; mkdir img
$ tar xvfz 5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b -C img/ ##解壓縮看裡面目錄內容
$ tree -L 1 img
img
├── bin
├── dev
├── etc
├── home
├── lib
........
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
```
## 認識 Docker(碼頭工人)

* docker 就是負責創造container跟監控container,container去使用網路,資料的讀取,並不會透過docker。

* Docker Engine會叫containerd去產生container,然後containerd只是負責把image unpack 變成 bundle,他並沒有去下載image,真正下載image的是docker。
* 再來做出container的是runc。但runc沒有做overlay2跟network。
* Docker Engine真正做的是overlay2跟network,還有下載image。
* runc產生container後就結束動作了,之後shim(墊片)會去監控container。
### 安裝 Docker
```
$ sudo apk add docker
(1/7) Installing ip6tables (1.8.7-r1)
(2/7) Installing ip6tables-openrc (1.8.7-r1)
(3/7) Installing tini-static (0.19.0-r0)
(4/7) Installing docker-engine (20.10.7-r2)
(5/7) Installing docker-openrc (20.10.7-r2)
(6/7) Installing docker-cli (20.10.7-r2)
(7/7) Installing docker (20.10.7-r2)
Executing docker-20.10.7-r2.pre-install
Executing busybox-1.33.1-r3.trigger
OK: 1398 MiB in 249 packages
設定 Docker Daemon 主機開機時自動啟動
$ sudo rc-update add docker boot
將 bigred 帳號加入 docker 群組後, 就不需使用 sudo 命令執行 docker
$ sudo addgroup bigred docker
重新開機 (一定要執行)
$ sudo reboot
```
檢視 Docker 運作資訊
```
$ docker info
.........
Images: 0
Server Version: 20.10.7
Storage Driver: overlay2
.......
沒有任何 Container 執行時, 只有 dockerd 及 containerd 這二個 Daemon 在運作
$ ps aux | grep -v grep | grep dockerd
3904 root 0:00 supervise-daemon docker --start --retry TERM/60/KILL/10 --stderr /var/log/docker.log --stdout /var/log/docker.log /usr/bin/dockerd --##supervise-daemon docker daemon如果被關閉了,那supervise-daemon就會把他啟動
3906 root 0:00 /usr/bin/dockerd
$ ps aux | grep -v grep | grep containerd
4070 root 0:06 containerd --config /var/run/docker/containerd/containerd.toml --log-level info ##containerd就是負責產生container
```
執行 Container
```
$ docker run --rm -d quay.io/cloudwalker/alpine sleep 120 ##只要container停止作業--rm就會停止container
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
cbdbe7a5bc2a: Pull complete
Digest: sha256:9a839e63dad54c3a6d1834e29692c8492d93f90c59c978c1ed79109ea4fb9a54
Status: Downloaded newer image for alpine:latest
$ ps aux | grep -v grep | grep 'containerd-shim'
15343 root 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id d25467bf48d51d0391096fcd76bb2ff6d29499cb7440feb8f0b61301de18b538 -address /var/run/docker/containerd/containerd.sock
```
* dockerd 就是docker的daemon,docker engine一啟動會跑很多的daemon,有很多的daemon就有很多的資安問題,因此使用podman,他可以做出95%docker的功能,並且他沒有daemon,沒有daemon就少了資源的消耗,資安問題也相對更安全。
```
bigred@alp:~$ ps aux | grep -v grep | grep dockerd
root 2751 0.0 0.0 936 500 ? S Jun25 0:00 supervise-daemon docker --start --retry TERM/60/KILL/10 --stderr /var/log/docker.log --stdout /var/log/docker.log /usr/bin/dockerd --
root 2753 0.0 3.2 777160 65336 ? Ssl Jun25 0:10 /usr/bin/dockerd
```
### Docker 原生橋接網路架構圖

* 只要有安裝docker 就一定會有docker0這張網路卡。
* docker0就是虛擬橋接器,讓這台機器的所有container都能互相連通。
* nat功能,讓裡面裡面的container可以透過etho上網。
```
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242f62d81b6 no
```
* docker今天所創造的網路,是別人幫忙的,用很久以前linux的一個核心功能,透過brctl所產生的,有虛擬橋接器和虛擬網路卡。
```
##container會透過nat上網,container的ip一定都是172.17
$ sudo iptables -t nat -L | grep 172.17
MASQUERADE all -- 172.17.0.0/16 anywhere
```
### 內建橋接網路運作架構
```
##產生container一定要有image
$ docker run --rm -itd quay.io/quay/busybox sh
363b728772742a06d91733b81b25344016d1b8d062bdd5d14b29741827871f5
##這邊看到container一啟動這邊多了一個網路介面veth4d8bf88
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02429b11cfa0 no veth4d8bf88
........
$ ifconfig veth4d8bf88
veth4d8bf88 Link encap:Ethernet HWaddr 3e:69:19:3e:c9:fe
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
:::
$ docker stop 363b
##container關掉後介面就不見了,他是動態的。
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02429b11cfa0 no
.........
```
List all containers (only IDs)
$ docker ps -aq
Stop all running containers
$ docker stop $(docker ps -aq)
Remove all containers
$ docker rm $(docker ps -aq)
Remove all images
$ docker rmi $(docker images -q)

* 只要container做`ping www.hinet.net` 這個動作,他首先會去192.168.61.2這個dns server 去做名稱解析,抓到這個網站的ip位址,有了ip之後電腦才會知道他在哪裡,再來就去203.68.12.10這個ip位址做溝通。
* 網址是給人記的,ip是給電腦讀的。
```
# Docker Host DNS IP
##container裡面的DNS會跟host 主幾一樣,192.168.61.2這個DNS是vmware提供的,因為我們現在這個docker host是vmware做的
$ cat /etc/resolv.conf
nameserver 192.168.61.2
$ docker run --rm quay.io/quay/busybox cat /etc/resolv.conf
nameserver 172.16.119.1
[註] 內定與 Docker Host (ALP) 相同 DNS IP. If the container cannot reach any of the IP addresses you specify, Google’s public DNS server 8.8.8.8 is added, so that your container can resolve internet domains
```
### Published ports,讓別人能連線使用
```
##nginx這個image就是一個網站,-p port號,網站預設port就是80
$ docker run --rm --name n1 -p 8080:80 -d quay.io/cloudwalker/nginx
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ad82bd0803fe nginx "nginx -g 'daemon of…" 47 seconds ago Up 46 seconds 0.0.0.0:8080->80/tcp n1
$ curl http://localhost:8080
.........
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ docker stop n1
##因為container還在啟動中,所以要加-f 才可以強制把container刪除
$ docker rm -f df280
```

container自己的網站port是80,他會連到docker host的8080port,之後再連到瀏覽器。
## 新增 Docker 網路
```
##docker產生網路,但其實不是docker做的,實際上也是linux很久的核心技術,虛擬橋接網路(brctl)
$ docker network create mynet1
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0a2fe39f1169 bridge bridge local
09412be1f561 host host local
408a59813ccd mynet1 bridge local
2a0277957c50 none null local
bigred@alp:~$ brctl show
bridge name bridge id STP enabled interfaces
br-408a59813ccd 8000.0242a14a48a6 no
docker0 8000.02429f066f90 no
顯示 mynet1 的 Network ID
$ docker network inspect --format='{{range .IPAM.Config}}{{.Subnet}}{{end}}' mynet1
172.18.0.0/16
$ docker network create --driver=bridge --subnet=192.168.166.0/24 --gateway=192.168.166.254 mynet2
9082207516ec857dcf64748486824444947224b8ccb58fc3c02e7d07e73df750
##mynet1是我們創的 跟mynet2是我們自己設定的
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0a2fe39f1169 bridge bridge local
09412be1f561 host host local
408a59813ccd mynet1 bridge local
9082207516ec mynet2 bridge local
2a0277957c50 none null local
$ sudo iptables -t nat -L -n | grep MASQUERADE
MASQUERADE all -- 192.168.166.0/24 0.0.0.0/0
MASQUERADE all -- 172.18.0.0/16 0.0.0.0/0
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
```
自己建立網卡
```
##使用我們自己建立的mynet1
$ docker run --rm --net mynet1 --name a1 -h ssn763 quay.io/cloudwalker/alpine cat /etc/hosts
127.0.0.1 localhost
...........
172.18.0.2 ssn763
$ docker run --rm --net mynet2 --name a1 -h ssn763 quay.io/cloudwalker/alpine hostname -i
192.168.166.1
* hostname -i 這命令根據本機電腦名稱, 從 /etc/hosts 取出這部電腦的 IP
```
設定固定 IP 位址
```
##--ip設定container的固定ip
$ docker run --net mynet2 --ip=192.168.166.3 --name a2 -h ddg52 -idt quay.io/cloudwalker/alpine sh
##進入container,它的ip 192.168.166.3
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:A6:03
inet addr:192.168.166.3 Bcast:192.168.166.255 Mask:255.255.255.0
設定 Container 雙網卡
##給container第二張網卡
$ docker network connect mynet1 a2
檢視 /etc/hosts 檔案, 命令如下 :
$ docker exec a2 cat /etc/hosts
127.0.0.1 localhost
.......
192.168.166.3 ddg52
172.18.0.2 ddg52
[重要] 如使用 busybox image 建置的 Container , 無法提供雙網卡功能
##只要是自己建的網路,它的DNS一定會設成127.0.0.11
$ docker exec a2 cat /etc/resolv.conf
search localdomain
nameserver 127.0.0.11
```
### Docker Embedded DNS server

使用自己建立的網卡,就會連到docker engine
* docker engine,他會紀錄所有container的名字記錄起來,因此之後container去ping別台container的名稱會通。
* 如果要使用ping container的名字,一定要使用docker engine。
docker0原生的橋接網路,就不能讓container透過名稱互通。
## 增加網卡

```
$ sudo nano /etc/network/interfaces
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
auto eth1
iface eth1 inet manual ##這片是設定我等一下可以操作,可以讓他使用dhcp,也可以自己設固態ip
$ sudo reboot
```
```
##--cap-add=NET_ADMIN:讓這個container可以去使用實體主機的網卡,沒給的話會被capabilities給限制住
$ docker run --name d1 --cap-add=NET_ADMIN --net='none' -itd quay.io/cloudwalker/alpine sh
3879c04ac6435e5cd0a64effdc5843bce57c3b1e4f277722806f622af53d4425
將 eth1 網卡加入 d1 貨櫃主機的 Network Namespace
dknet是老師寫的程式,讓d1 container直接使用host alp的實體網卡eth1
$ dknet d1 eth1
在 ALP 主機中, 此時看不到 eth1 網卡
$ ifconfig eth1
eth1: error fetching interface information: Device not found
```
* 給container直接使用host alp的網卡,那container的ip就要設host alp的同個網段(192.168.61.xx)。
```
$ docker exec -it d1 sh
##啟動網卡
/ # ifconfig eth1 up
/ # ifconfig eth1
eth1 Link encap:Ethernet HWaddr 00:50:56:31:2D:C9
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:3 errors:0 dropped:0 overruns:0 frame:0
..............
##使用公司設好的dhcp server來做這張網卡的ip分配
/ # udhcpc -i eth1 -q
udhcpc: started, v1.32.0
udhcpc: sending discover
udhcpc: sending select for 192.168.72.152
udhcpc: lease of 192.168.72.152 obtained, lease time 1800
/ # route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.72.2 0.0.0.0 UG 203 0 0 eth1
192.168.72.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
```
如果container關掉,那張獨佔網卡就會退回host alp。
```
bigred@alp:~$ docker stop d1
d1
bigred@alp:~$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
afb45989c956 quay.io/cloudwalker/alpine "sh" 17 minutes ago Exited (137) 8 seconds ago d1
ea36cb004c8c quay.io/cloudwalker/alpine "sh" 2 hours ago Exited (137) 30 minutes ago a1
20d268acff3c quay.io/cloudwalker/alpine "sh" 2 hours ago Exited (137) 30 minutes ago a2
```
```
bigred@alp:~$ docker start d1
d1
bigred@alp:~$ docker exec -it d1 sh
/ # ifconfig -a
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
```
container給他使用實體主機網卡用dhcp server配發ip;docker host重新開機,container會關閉,也要重新再給他網卡設定
```
bigred@alp:~$ cat bin/tap0.sh
#!/bin/bash
docker start d1
dknet d1 eth1
docker exec d1 ifconfig eth1 up
docker exec d1 udhcpc -i eth1 –q
```
# 建立第一個軟體貨櫃
image前面的名稱可以是公司,可以是個人
```
$ docker search quay.io/busybox
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
prometheus/busybox # Prometheus Busybox Docker Base Images [![… 0
libpod/busybox 0
quay/busybox Busybox base image. 0
domino/busybox 0
bhouse/busybox 0
openshifttest/busybox 0
```
## 建立軟體貨櫃 (再次確認 Linux Namespace 啟動)
```
$ docker run --name b1 -it quay.io/cloudwalker/busybox /bin/sh
/ # uname -r
5.10.27-0-virt
/ # hostname
54ab5a1a4690
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 /bin/sh
10 root 0:00 ps aux
##這裡是使用docker的網路,所以當然會通
/ # ifconfig eth0
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
.........
/ # exit
```
## 設定 n1 Container 自動重新啟動
```
##設定docker host重新啟動,container也重新啟動
$ docker run -d --restart=always --name n1 quay.io/cloudwalker/nginx
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ef58bd22baf0 nginx "/docker-entrypoint.…" 25 seconds ago Up 24 seconds 80/tcp n1
$ sudo reboot
```
## 問題
> 問題討論 (一) : 如何在 為何以下命令執行錯誤 ?
> $ docker run --rm --name=b1 -i -t dafu/busybox
> FATA[0000] Error response from daemon: No command specified
> 答:因為這個image沒有內定要跑的貝殼程式,所以自己要在後面加上
> 問題討論 (二) : 為何以下命令執行錯誤 ?
> $ docker run --rm --name=b2 -i -t dafu/busybox /bin/bash
> FATA[0000] Error response from daemon: Cannot start container 1b1c5433af380fa2e5abb0c6b5b0b37ebb597573286e1db5e05b7cb9ec17d3fb: exec: "/bin/bash": stat /bin/bash: no such file or directory
> 答:因為這個image裡面的檔案沒有/bin/bash這個程式,所以他才找不到。
> 問題討論 (三) : 為何 Container 系統中不能修改日期與時間 ?
> $ docker run --rm --name=b3 -it quay.io/cloudwalker/alpine
> / # date --set "2006-10-10 18:00:00"
> date: can't set date: Operation not permitted
> Tue Oct 10 18:00:00 UTC 2006
> / # exit
> 答:因為他被capabilities給限制住。
>
> / # apk add libcap ##安裝capabilities
> / # capsh --print
Current:cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=ep
##這裡面沒有可以修改時間的能力,『CAP_SYS_TIME』
> ##增加CAP_SYS_TIME功能,讓這個container有修改時間的能力(注意改完時間記得改回來)
> $ docker run --rm --name=b3 -it --cap-add CAP_SYS_TIME quay.io/cloudwalker/alpine
> / # date --set "2006-10-10 18:00:00"
> Tue Oct 10 18:00:00 UTC 2006
> / # exit
## 建立 Alpine 軟體貨櫃主機
```
$ docker run --name a1 -it quay.io/quay/alpine sh
/ # busybox | grep httpd
[重要] Docker Alpine Image 內建的 busybox 並沒提供 httpd 功能
下載 Busybox 執行檔
/ # wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
安裝 Busybox 執行檔
/ # chmod +x busybox-x86_64; mv busybox-x86_64 bin/busybox
執行 Busybox 命令
/ # busybox | grep httpd
hexedit, hostid, hostname, httpd, hush, hwclock, i2cdetect, i2cdump, i2cget, i2cset, id, ifconfig,
```
```
製作網站目錄及首頁
/ # mkdir www; echo '<h1>Busybox HTTPd</h1>' > www/index.html
啟動 Busybox Httpd 網站伺服器
/ # busybox httpd -p 8888 -h www
安裝網頁工具
/ # apk update; apk add elinks curl
取得網頁
/ # curl http://localhost:8888
<h1>Busybox HTTPd</h1>
檢視網頁
/ # elinks -dump 1 http://localhost:8888
Busybox HTTPd
/ # exit
```
```
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
559336d9d286 alpine "sh" 9 minutes ago Exited (0) 2 minutes ago a1
$ docker commit a1 <Login Name>/a1
sha256:8684b18333c9b1bf1a87ff008819d554459d752739e4c312c515b5031285ef2a
$ docker login
.........
Username: <Login Name>
Password:
Login Succeeded
$ docker push <Login Name>/a1
$ docker logout
[重要] 關閉 Docker Host 之前, 記得執行 docker logout, 然後刪除 /home/bigred/.docker 目錄
$ docker rm a1
```
```
$ docker run --name testa1 -d <login name>/a1 httpd -p 8888 -h www
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aa9b759e6f0e dafu/busyweb "httpd -p 8888 -h www" 6 seconds ago Exited (0) 4 seconds ago testa1
因 httpd 沒在前景執行, 以致 testa1 貨櫃主機沒啟動
$ docker rm testa1
```
* 重新建立 testweb 貨櫃主機
* container前景一定要有程式在執行,前景沒有程式,container一定run不起來
```
##-f foreground
$ docker run --name testa1 -d <login name>/a1 httpd -f -p 8888 -h www
$ docker exec testa1 hostname -i
172.17.0.2
$ curl http://172.17.0.2:8888
<h1>Busybox HTTPd</h1>
以強制方式移除 testa1
$ docker rm -f testa1
# Dockerfile

```
* 使用docker history可以看到image內定的執行命令
```
##內定執行nginx
##-g 就是要在前景執行
$ docker history quay.io/cloudwalker/nginx
IMAGE CREATED CREATED BY SIZE COMMENT
d1a364dc548d 15 months ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 15 months ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
```
## 準備 Docker Image 的系統目錄
```
$ mkdir -p ~/r2d2/rootfs/{bin,sbin,usr/{bin,sbin},dev,lib,proc}; cd ~/r2d2
##rootfs裡面放的有busybox,他提供一堆linux的功能,還有sh跟sh 的相依檔。
$ cp /bin/sh rootfs/bin; cp /lib/ld-musl-x86_64.so.1 rootfs/lib/; cp /bin/busybox rootfs/bin/; sudo chown -R root:root rootfs
##安裝busybox到rootfs
$ sudo chroot rootfs /bin/busybox --install -s
$ sudo chroot rootfs /bin/sh
@alp:/$ find / -name busybox | wc -l
1
@alp:/$ exit
##壓縮rootfs檔案,取名叫r2d2.tar.gz
$ cd rootfs; tar zcvf ../r2d2.tar.gz .; cd ..
```
> 問題:為什麼在/bin/sh這個process裡面,還要再裝busybox
> 答:因為/bin/sh是一個貝殼程式,有貝殼程式之後還要需要busybox裡面的那些命令,才可以來操作他
## Distroless images
* Distroless images 只有包含你的應用程式與其相依檔,並不包含標準linux發行版中的其他程式(如軟體套件管理系統、貝殼程式等皆不包含)。
### 自製與使用原生 Distroless images
* 貨櫃化要成功,必須要自己做出image給自己用
```
##第一個字一定要是大寫的D
##from 就是現在你要做的image,是要從哪個image來的,改裝的概念。
##scratch是空白的image,所以是原生。
##環境變數PATH=/bin:/usr/bin:/sbin:/usr/sbin目錄有這些,container會用到的環境變數。
##ADD他會自動幫我們解壓縮,解完壓縮就會存到我們現在做的image的根目錄。
##CMD 就是container內定要執行的命令。
$ nano Dockerfile
FROM scratch
ENV PATH=/bin:/usr/bin:/sbin:/usr/sbin
ADD r2d2.tar.gz /
CMD ["/bin/sh"]
##建立image,docker build內定就是去執行Dockerfile,.就是會在這個目錄區去找image
## --no-cache 如果本機沒有這個image,確保上網抓的image一定是最新版本
$ docker build --no-cache -t r2d2 .
$ docker images | grep r2d2
r2d2 latest 61d53f8cccae 13 minutes ago 2.28MB
##啟動container
$ docker run --rm --name r2d2 -it r2d2
/ # ps
PID USER TIME COMMAND
1 0 0:00 /bin/sh
7 0 0:00 ps
/ # exit
```
> 問題:請問這台container是什麼作業系統?
> 答:Dockerfile創造出來的就是distroless image,這台container跑的只有他要的應用系統跟他的相依檔,他甚至不用使用貝殼程式,可以只鎖定企業真正的需求的Application。
## 開發 CGI with Golang 網站
* CGI(common gateway interface) 他可以使用任何語言來run網站,但最終在run的都是bash script
* CGI(common gateway interface)就是使用bash script跑起來的網站,下面的練習網站是用go語言寫的
* UX UI網頁工程師:架網站的,他們需要的職能就是CGI

* 在瀏覽器輸入`http://120.96.143.200:8080`就會連接到 golang 語言的網站。go 語言的網站只要執行到 "/" 就會啓動 `cgichild.sh` 這個程式,然後再把執行完的結果透過網路傳回電腦。
```
$ mkdir -p ~/wulin/gocgi; cd ~/wulin/gocgi
##執行程式的時候只要有 "/" 就會跑cgiHandler 這個function
##cgichild.sh 他就是bash script<,這個程式是真正在執行的
$ echo 'package main
import (
"net/http"
"net/http/cgi"
)
func cgiHandler(w http.ResponseWriter, r *http.Request) {
handler := cgi.Handler{Path: "cgichild.sh"}
handler.ServeHTTP(w, r)
}
func main() {
http.HandleFunc("/", cgiHandler)
http.ListenAndServe(":8080", nil)
}' > main.go
```
```
$ go mod init gocgi
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main
##這邊 echo 顯示出來的資料不是顯示在自己的螢幕上,而是傳到瀏覽器上面
##echo "Content-type: text/html" 標籤的方式傳資料給瀏覽器,代表等一下會傳html標籤給你
## echo "" 這裡代表換行,是瀏覽器看得懂的
##<br/> 在網頁代表換行
##/usr/bin/env linux的命令,顯示自己環境變數,輸出到前端瀏覽器,代表可以在這裡下linux的命令,例如mysql
$ echo $'#!/bin/sh
echo "Content-type: text/html"
echo ""
echo \'<html><body>\'
echo \'Hello From Bash <br/>Environment:\'
echo \'<pre>\'
/usr/bin/env
echo \'</pre>\'
echo \'</body></html>\'
exit 0 ' > cgichild.sh
$ chmod +x cgichild.sh
```
## 自製 CGI with Golang Image
```
$ echo 'FROM scratch
ADD main /
ADD cgichild.sh /
CMD ["/main"] ' > Dockerfile
$ docker build -t gocgi .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gocgi latest 4b8dcd855475 9 seconds ago 6.6MB
.....
```
* 因爲 Dockerfile 做出來的 image 裡面沒有 `cgichild.sh` 程式中會用到的 `/bin/sh , cut, grep, free` 這些命令, 所以才會執行失敗
```
$ docker run --rm --name g1 -d -p 8080:8080 gocgi
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2713c70b789f gocgi "/main" 22 seconds ago Up 22 seconds 0.0.0.0:8080->8080/tcp g1
以下命令為何沒有顯示任何資訊
$ curl http://localhost:8080
$ docker logs g1
2022/08/23 15:54:39 CGI error: fork/exec cgichild.sh: no such file or directory
* 因 cgichild.sh 程式中的 /bin/sh 命令不存在
$ docker stop g1
```
* 重製 CGI with Golang Image
```
$ echo 'FROM quay.io/cloudwalker/alpine
ADD main /
ADD cgichild.sh /
CMD ["/main"] ' > Dockerfile
$ docker rmi gocgi; docker build -t gocgi .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gocgi latest 9b04c165d217 8 seconds ago 12.2MB
.....
```
## 開發 Golang 網站
```
$ mkdir ~/wulin; cd ~/wulin; mkdir mygo; cd mygo
##別人連過來打的網址就是/,他會連到handler,然後他會在瀏覽器去顯示Hello, 世界
##import就是這個程式用到的相依檔
$ echo 'package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, 世界")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8888", nil))
}' > main.go
##編譯go語言
$ go mod init mygo
##把這個程式以及他所需的相依檔會寫在main
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main
```
```
$ echo 'FROM scratch
ADD main /
CMD ["/main"] ' > Dockerfile
$ docker build -t goweb .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
goweb latest 7f9652539fc0 14 minutes ago 6.13MB
```
## 檢視原廠 Image 內定執行命令
```
$ docker run --rm -it alpine
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
7 root 0:00 ps
/ # exit
$ docker run --rm -it busybox
/ # exit
問題 : 上述二個命令中, 不需指定執行命令, 一樣可以啟動貨櫃主機, 請問內定執行命令為何 ?
使用 docker history 命令得知 Docker Image 內定執行命令
$ docker history alpine
IMAGE CREATED CREATED BY SIZE
3fd9065eaf02 4 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 4 months ago /bin/sh -c #(nop) ADD file:093f0… 4.15MB
$ docker history busybox
IMAGE CREATED CREATED BY SIZE
8ac48589692a 6 weeks ago /bin/sh -c #(nop) CMD ["sh"] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ADD file:c94ab8f8614 … 1.15MB
```
```
$ sudo apk add jq
$ dktag golang | grep -E "^1.1.-*alpine$"
......
1.16-alpine
1.17-alpine
##上面的image是暫時的,真正產生image是下面那段,1.18-alpine這個image裡面有go語言要的環境
$ echo 'FROM golang:1.18-alpine AS build
WORKDIR /src/
COPY main.go /src/
RUN go mod init mygo && CGO_ENABLED=0 go build -o /bin/demo
FROM scratch
COPY --from=build /bin/demo /bin/demo
ENTRYPOINT ["/bin/demo"] ' > Dockerfile
$ docker build -t goweb .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
goweb latest a356ba6caa9e 7 seconds ago 7.41MB
golang 1.14-alpine 285e050bfca6 2 days ago 370MB
```
## 自製 Docker Image 實務範例
* 撰寫 alpine.base Dockerfile
* 讓這個image有很多功能,因為資安的人會想要進來檢查,就以要替他們做好
* image裡面要裝的內容自己決定,需要用到才要安裝
```
$ mkdir -p ~/wulin/base; cd ~/wulin
$ echo 'FROM alpine:3.14.0
RUN apk update && apk upgrade && apk add --no-cache nano sudo wget curl \
tree elinks bash shadow procps util-linux coreutils binutils findutils grep bind-tools && \
wget https://busybox.net/downloads/binaries/1.28.1-defconfig-multiarch/busybox-x86_64 && \
chmod +x busybox-x86_64 && mv busybox-x86_64 bin/busybox1.28 && \
mkdir -p /opt/www && echo "let me go" > /opt/www/index.html
CMD ["/bin/bash"] ' > base/Dockerfile
```
建立 alpine.base image
```
$ docker build --no-cache -t alpine.base base/
執行 alpine.base image 內定命令
$ docker run --rm -it alpine.base
bash-5.0# /bin/busybox1.28 | head -n 1
BusyBox v1.28.1 (2018-02-15 14:34:02 CET) multi-call binary.
bash-5.0# /bin/busybox | head -n 1
BusyBox v1.30.1 (2019-06-12 17:51:55 UTC) multi-call binary.
bash-5.0# busybox httpd -h /opt/www
httpd: applet not found
bash-5.0# busybox1.28 httpd -h /opt/www
bash-5.0# curl http://localhost
let me go
bash-5.0# exit
exit
```
直接啟動網站
```
##這個process一定要在前景執行
$ docker run --rm --name b1 alpine.base busybox1.28 httpd -h /opt/www
$ docker exec -it b1 bash
Error: No such container: b1
##-f forground前景執行
Container 中的第一個啟動 process 必須前景執行, 這樣 Container 才能保持在執行狀態
$ docker run --rm --name b1 -d -p 80:80 alpine.base busybox1.28 httpd -f -h /opt/www
9a61cf33c20ca7d1ac463ab4a2c55676aac4f2c808d3d204e909edfc11d0c3ff
$ curl http://localhost
let me go
$ docker stop b1
```
## 自製 OpenSSH Image
```
##image分上下層就因為保護
$ cd ~/wulin; mkdir sshd
##把alpine.base這個image做改裝
##ENTRYPOINT是強制性的內定命令,不可能在run其他的程式
##CMD ["-D"] 這裡是宣告ENTRYPOINT的參數,他被降級了;-D是叫/usr/sbin/sshd 這個程式一定要在前景執行
$ cat sshd/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 ALP SSHd 6000\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 && \
rm /sbin/reboot && rm /usr/bin/killall
EXPOSE 22
ENTRYPOINT ["/usr/sbin/sshd"]
CMD ["-D"]
[重要] ENTRYPOINT 所指定的執行命令, 在建立 Container 時強制執行此命令並且不可被其它命令取代
```
## 建立與使用 alpine.sshd image
```
$ docker build --no-cache -t alpine.sshd sshd/
$ docker run --rm --name s1 -h s1 alpine.sshd sh
Extra argument sh.
[重要] 因使用 entrypoint 宣告內定命令, 便無法自行指定執行命令
建立 s1 container
##-d 把container推到背景執行
$ docker run --rm --name s1 -h s1 -d -p 22100:22 alpine.sshd
```
登入 s1 貨櫃主機
```
$ ssh bigred@localhost -p 22100
bigred@192.168.122.47's password: bigred
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.3 0.0 4328 2944 pts/0 Ss+ 09:11 0:00 /usr/sbin/sshd -D
root 9 0.1 0.0 4356 3500 ? Ss 09:12 0:00 sshd: bigred [priv]
bigred 11 0.0 0.0 4356 2340 ? R 09:12 0:00 sshd: bigred@pts/1
........
s1:~$ sudo reboot
sudo: reboot: command not found
s1:~$ sudo killall sshd
sudo: killall: command not found
* 上面二個命令在製作 alpine.sshd images 時被移除, 因這二個命令結合 sudo 命令, 會將 s1 Container 關掉
$ exit
$ docker stop s1
```
> 問題:在container裡面建立帳號,跟在Dockerfile建立帳號個有什麼缺點?
> 答:在container裡面建帳號如果container刪除了,在重建container的時候那些帳號也會消失。
> 在Dockerfile建帳號,如果現在要再新增一個帳號的話就要先把原本的container跟他就的image刪除,再重新build一個新的image,並且做出來的image沒有做消磁的話,那些使用者帳號跟密碼都可以用 ‘docker history --no-trunc’ 看到所有資訊。
## Docker Image 備份與還原
`##--no-trunc這個參數可以看到做image的完整過程
$ docker history --no-trunc alpine.sshd`
```
$ cd ~/wulin
$ docker history alpine.sshd
IMAGE CREATED CREATED BY SIZE COMMENT
eb14bc497f7c 5 hours ago /bin/sh -c #(nop) CMD ["-D"] 0B
1e05936fd376 5 hours ago /bin/sh -c #(nop) ENTRYPOINT ["/usr/sbin/ss… 0B
c5c25dc4a60c 5 hours ago /bin/sh -c #(nop) EXPOSE 22 0B
6a42f8c6183f 5 hours ago /bin/sh -c apk update && apk add --no-ca… 4MB
37dfd3d3318e 6 hours ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
86c8cd04a262 6 hours ago /bin/sh -c apk update && apk upgrade && apk … 36.4MB
f70734b6a266 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:b91adb67b670d3a6f… 5.61MB
備份 Image
##把image變成打包檔
$ docker save alpine.sshd > alpine.sshd.tar
```
還原 Image
```
##先刪除舊的image
$ docker rmi alpine.sshd
##還原 Image
$ docker load < alpine.sshd.tar
$ docker history alpine.sshd
IMAGE CREATED CREATED BY SIZE COMMENT
eb14bc497f7c 5 hours ago /bin/sh -c #(nop) CMD ["-D"] 0B
1e05936fd376 5 hours ago /bin/sh -c #(nop) ENTRYPOINT ["/usr/sbin/ss… 0B
c5c25dc4a60c 5 hours ago /bin/sh -c #(nop) EXPOSE 22 0B
6a42f8c6183f 5 hours ago /bin/sh -c apk update && apk add --no-ca… 4MB
37dfd3d3318e 6 hours ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
86c8cd04a262 6 hours ago /bin/sh -c apk update && apk upgrade && apk … 36.4MB
f70734b6a266 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:b91adb67b670d3a6f… 5.61MB
[重點] 還原的 image 的目錄架構, 與原先 Image目錄架構一樣
```
## Docker Container 備份與還原(消磁)
備份container,還原時變成image
```
$ docker run --name s2 -h s2 -d alpine.sshd
6e52bd5aee18c0aed8dbb17920037be0b9109158329b401c56b4f64417c2d784
$ docker exec -it s2 bash
$ sudo useradd -m -s /bin/bash rbean
$ echo "rbean:rbean" | sudo chpasswd
$ exit
[重要] 使用上述命令建立 s2 Container, 這個 Container 有啟動 openssh server, 所以這個 Container 有 openssh 的執行狀態資訊檔, 這時使用 docker export 命令
匯出的 Tar 檔中就會有殘留 openssh 的執行暫存檔, 以至後續做出的 image 無法啟動 openssh server, 所以必須先關閉 s2 container, 才可備份此 container
##關閉container
$ docker stop s2
##把貨櫃做出口,container變成打包檔
$ docker export s2 > s2.tar
```
```
##把container刪除,image也跟著刪除
$ docker rm s2 && docker rmi alpine.sshd
##備份container但是還原的時候變成image
$ docker import s2.tar alpine.sshd
$ docker history alpine.sshd
IMAGE CREATED CREATED BY SIZE COMMENT
00a487305c5b 12 seconds ago 44.3MB Imported from -
```
* 把container做備份(export),之後再還原(import),回來變成image,就會被消磁。
* 消磁就是把做好的image給他備份,再把它做成container,再把container備份,最後把他還原成image,就會被消磁(沒有Dockerfile的內容)。
```
$ docker history alpine.sshd
IMAGE CREATED CREATED BY SIZE COMMENT
d1f2750ac6d6 12 minutes ago 50.8MB Imported from -
```
* 因為已經被消磁,所以沒有內定命令,因此自己要在後面加程式命令。
`$ docker run --name s3 -it alpine.sshd bash`
* 也可以使用/usr/sbin/sshd,使用ssh用帳號登入container
`$ docker run --rm --name s1 -d -p 22100:22 alpine.sshd /usr/sbin/sshd -D`
> 問題:如果在維護container不小心把它刪除怎麼辦?刪除container,裡面的資料也就歸零
> 答:比較好的辦法就是這個container裡面有做的系統維護設定,也在Dockerfile上面做,並且做註解,這樣就可以馬上把被刪除的container救回來。
# Docker Data Volume by pass overlay2

* 他會儲存在我們docker host的linux系統目錄
* 並且如果container刪除,資料還會存在,可以永存資料
## MariaDB Image 內建 Data Volume
```
$ docker pull quay.io/cloudwalker/mariadb
檢視 mariadb image 內部資訊
##VOLUME [/var/lib/mysql] 這裡宣告這個目錄不會使用overlay2檔案系統,而是by pass 到linux作業系統的目錄
$ docker history mariadb
IMAGE CREATED CREATED BY SIZE COMMENT
7aff94e60a52 2 weeks ago /bin/sh -c #(nop) CMD ["mysqld"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) EXPOSE 3306/tcp 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
<missing> 2 weeks ago /bin/sh -c ln -s usr/local/bin/docker-entryp… 34B
<missing> 2 weeks ago /bin/sh -c #(nop) COPY file:f73461a79523c327… 5.65kB
<missing> 2 weeks ago /bin/sh -c #(nop) VOLUME [/var/lib/mysql] 0B
.......
```
* 由以上資訊可得知 mariadb image 有建立 /var/lib/mysql 對應的 Data Volume
* 建立 m5 貨櫃主機
```
##建立mariadb container
$ docker run -d -p 3301:3306 -e MYSQL_ROOT_PASSWORD=admin --name m5 quay.io/cloudwalker/mariadb
檢視 m5 內建 Data Volume 目錄
##使用inspect看json檔,看這個container在linux檔案系統是存在哪個目錄
$ docker inspect --format='{{index .Mounts 0}}' m5 | cut -d' ' -f3
/var/lib/docker/volumes/7539e05831efe4887d4629b81184de82a4dbd13ae2a872cab892f2d4ec11bdc5/_data
```
> 問題:如果container刪除,在產生一次,他會用舊的目錄區,還是他又會再重新創建一個新的目錄區?
> 答:不會使用舊的目錄區,而是會在產生一個新的。
* container被刪除留在docker host的目錄還會存在,
```
$ docker rm -f m5
##檢視被遺棄的目錄區
$ docker volume ls -f dangling=true
DRIVER VOLUME NAME
local 7539e05831efe4887d4629b81184de82a4dbd13ae2a872cab892f2d4ec11bdc5
##清除沒在使用的目錄區
$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
7539e05831efe4887d4629b81184de82a4dbd13ae2a872cab892f2d4ec11bdc5
Total reclaimed space: 35.71MB
```
# 認識 Host Data Volume 運作架構

* 可以同時有兩個container使用這個目錄
* Host Data Volume =linux主機某個資料夾跟container內的資料夾連通
* 自己建目錄,web{config,logs}白色的目錄區部分
* 由(alp)linux主機掛載目錄到container 1及container 2,container裡web目錄的內容會和linux主機的web目錄相同。
使用 myderby Image
```
$ docker run --name d1 -d -p 8888:8888 quay.io/cloudwalker/alpine.derby
$ curl http://localhost:8888/db; echo ""
Database Name = Apache Derby<br>Database Version = 10.14.2.0 - (1828579)<br>Driver Name = Apache Derby Embedded JDBC Driver<br>
$ curl http://localhost:8888/hostname; echo ""
Hostname : 3d7b93088d15
$ docker rm -f d1
d1
```
指定container存檔目錄
```
$ cd ~/wulin; mkdir db
## -v 指定目錄 ~/wulin/db:/derby/db 冒號前面是docker host的目錄,冒號後面的是container的目錄
$ docker run --name w1 -d -p 8888:8888 -v ~/wulin/db:/derby/db quay.io/cloudwalker/alpine.derby
$ curl http://localhost:8888/db/cars/create;echo ""
cars table created
$ curl 'http://localhost:8888/db/cars/insert?id=123&name=star&price=123';echo ""
Add Records ok
$ curl 'http://localhost:8888/db/cars/insert?id=234&name=sun&price=123';echo ""
Add Records ok
$ curl http://localhost:8888/db/cars/list;echo ""
123,star,123<br>234,sun,123<br>
```
## 認識 Process

* 執行程式一定會跑到記憶體裡面去執行,在記憶體裡面就是process。
* process有分三個部分,PID,UID,程式碼。
條列式顯示所有程序
取出bigred執行的所有程式
```
##這裡的 -e 參數是代表輸出所有程序的資訊,而 -o 參數則是用來指定輸出欄位用的,後面接著所有想要輸出的欄位名稱。
$ ps -eo user,pid,ruid,euid,cmd | grep bigred
root 3497 0 0 /bin/login -f bigred
bigred 3507 1000 1000 -bash
bigred 3533 1000 1000 dialog --title Cloud Native Trainer --textbox /tmp/sinfo 24 85
root 3599 0 0 sshd: bigred [priv]
bigred 3604 1000 1000 sshd: bigred@pts/0
bigred 3605 1000 1000 -bash
bigred 4589 1000 1000 ps -eo user,pid,ruid,euid,cmd
bigred 4590 1000 1000 grep bigred
```
ruid:是誰去執行程式
euid:真正執行使用者的id,程式在跑是他來決定,是他有這個程式的執行權限,就是他來執行這格程式的執行代碼。
[註] 在 Alpine Linux 系統中 , 內定 ps 命令是由 busybox 執行, 如要執行全功能 ps 命令, 請執行 sudo apk add procps
```
##在根目錄下面建立檔案
$ cd; mkdir setuid; cd setuid
$ echo 'package main
import (
"fmt"
"io/ioutil"
"os/exec"
)
func main() {
cmd := exec.Command("sleep", "60")
cmd.Run()
err := ioutil.WriteFile("/mulan.txt", []byte("Hello"), 0755)
if err != nil {
fmt.Printf("Unable to write file: %v\n", err)
} else {
fmt.Printf("/mulan.txt created\n")
}
} ' > myfile.go
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a myfile.go
```
取得 ALP 的 bigred 帳號 ID
```
$ echo $UID
1000
$ dir myfile
-rwxrwxr-x 1 bigred bigred 2.0M Jul 26 05:13 myfile
bigred 帳號沒有權限在 根目錄 (/) 產生檔案
$ ./myfile &
[1] 4698
$ ps -eo user,pid,ruid,euid,cmd | grep myfile
bigred 4698 1000 1000 ./myfile
bigred 4715 1000 1000 grep myfile
bigred@alp:~/setuid$ Unable to write file: open /mulan.txt: permission denied
```
* 這個程式前面會睡60秒,再來他會在根目錄建立一個mulan.txt檔案,後面會失敗是因為bigred沒有權限在根目錄底下建立檔案。
## Setuid
* 只要有執行setuid,任何使用者在執行這個程式的時候,都會使用這個程式的owner的權限去執行他。
回到原先終端機, 一定要先設定 owner, 才可設定 setuid
```
$ sudo chown root myfile; sudo chmod 4755 myfile
$ dir myfile
-rwsr-xr-x 1 root bigred 2.0M Jul 26 06:57 myfile
$ ./myfile &
/mulan.txt created
$ ps -eo user,pid,ruid,euid,cmd | grep myfile
root 4983 1000 0 ./myfile
bigred 4994 1000 1000 grep myfile
```
檢視 系統中有多少具有 setuid 功能的 命令
```
##找所有setuid的程式
$ sudo find / -user root -perm -4000 2>/dev/null | grep -E '^/bin|^/usr/bin'
/usr/bin/fusermount3
/usr/bin/sudo
/bin/mount
/bin/bbsuid
/bin/umount
/bin/ping
/bin/fusermount
##ping這個命令,不管是誰來執行他,都是透過root的權限來執行他。
bigred@alp:~/setuid$ ls -al /bin/ping
-rwsr-xr-x 1 root root 64120 Apr 16 06:51 /bin/ping
```
> 結論:有setuid的命令越少越好,因為他會很容易成為駭客攻擊的弱點。
> 問題:為什麼passwd這個命令可以去改shadow這個檔案的內容?
> 答:passwd這個命令是一個捷徑檔,真正在執行的是bbsuid,他也是有執行setuid,所以大家執行他是透過root的權限。
```
bigred@alp:~/setuid$ ls -al /etc/shadow
-rw-r----- 1 root shadow 774 Jul 10 2021 /etc/shadow
```
```
bigred@alp:~/setuid$ ls -al /usr/bin/passwd
lrwxrwxrwx 1 root root 11 Jul 3 22:29 /usr/bin/passwd -> /bin/bbsuid
bigred@alp:~/setuid$ ls -al /bin/bbsuid
---s--x--x 1 root root 14144 Jun 12 00:59 /bin/bbsuid
```
## Container 與 系統帳號
指定 Container 執行的帳號
```
##sleep infinite睡得無窮無盡
$ docker run --rm -it -d --name u1 quay.io/cloudwalker/alpine sleep infinite
e86b69256c3403b1657541e7c655b497f2550ff199cdb9dda936461ed26046bd
##container裡面有啟動user namespace,所以他在外跟在裡面的使用者都不同
$ docker exec u1 whoami
root
$ ps -eo user,pid,ruid,euid,cmd | grep "sleep infinity"
bigred 6578 1000 1000 grep sleep infinity
$ docker stop u1
##--user bigred宣告container內部的user是bigred,因為container裡面沒有bigred的帳號,所以會失敗
$ docker run --rm -it -d --name u1 --user bigred quay.io/cloudwalker/alpine
c9bfe7efb80929686b0e8786c9a5399a280478571cc2358bd0ab38f7487cf2cd
docker: Error response from daemon: unable to find user bigred: no matching entries in passwd file.
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
```
指定用 UID 1001 帳號執行以下 Container
```
$ docker run --rm -it -d --name u1 --user 1001 quay.io/cloudwalker/alpine
##建立container的使用者,跟container內部的使用者都是1001,container裡面跟docker host根本沒有1001這個使用者,但他還是能透過uid來啟動他,在linux是允許這個操作
$ ps -eo user,pid,ruid,euid,cmd | grep "/bin/sh$"
1001 5925 1001 1001 /bin/sh
$ docker stop u1
```
```
##雖然他名稱不叫Dockefile,但他就是Dockerfile;使用image來決定user
$ echo 'FROM quay.io/cloudwalker/alpine
RUN apk update && apk add tree nano
RUN addgroup appgroup
RUN adduser -u 1002 -G appgroup -D appuser
USER appuser
ENTRYPOINT ["sleep", "infinity"]' > myuser
##build image 產生image
$ docker build -t myuser - < myuser
$ docker run --rm --name u1 -d myuser
##USER appuser決定container內部的使用者id,但連docker host也是1002,所以如果駭客攻破container的user,進到我們docker host也是1002
$ ps -eo user,pid,ruid,euid,cmd | grep "sleep infinity"
1002 8129 1002 1002 sleep infinity
bigred 8180 1000 1000 grep sleep infinity
```
```
$ docker exec -it u1 sh
/ $ rm -rf /* &>/dev/null; echo $?
1
[註] 上面命令無法刪除任何系統檔案
/ $ tree /dev | wc -l
19
/ $ rm -rf /dev &>/dev/null
/ $ tree /dev | wc -l
19
$ exit
$ docker stop u1; docker rmi myuser
```
> 結論:container裡面只要是一般使用者他基本上很多東西都不能做。
```
$ id -u bigred; id -g bigred
1000
1000
$ mkdir ~/zzz; ls -lsd ~/zzz
4 drwxrwxr-x 2 bigred bigred 4096 Aug 22 09:46 zzz
$ docker run --rm -v ~/zzz:/opt --name u1 -d myuser
Container 中的 /opt 權限是套用 Docker Host ~/zzz 目錄權限
$ docker exec u1 ls -al /opt
total 8
drwxr-sr-x 2 1000 appgroup 4096 Jul 8 13:00 .
drwxr-xr-x 1 root root 4096 Jul 8 13:02 ..
```
> 問題:為什麼owner是 1000 ,而群組是appgroup?
> 答:docker host的目錄掛到container,container的目錄就會以docker host的權限做處理,container的使用者帳號沒有對應到1000的使用者,所以就顯示1000,而群組也是docker host的1000,到container他就會找1000對應到的是誰,群組對應到1000的是appgroup。
無法在剛剛掛載進來的zzz目錄做修改
```
$ docker exec -it u1 sh
/ $ whoami
appuser
/ $ mkdir /opt/vvv
mkdir: can't create directory '/opt/vvv': Permission denied
/ $ exit
$ chmod -R 777 ~/zzz
$ docker exec u1 mkdir /opt/vvv
$ ls -al ~/zzz
total 12
drwxrwsrwx 3 bigred bigred 4096 Jul 8 21:21 .
drwxr-sr-x 11 bigred bigred 4096 Jul 8 21:00 ..
drwxr-sr-x 2 1002 bigred 4096 Jul 8 21:21 vvv
```
> 掛載目錄進來以docker host的權限為主,在container操作目錄的時候是以container使用者的權限為主
## Linux System Call & Seccomp

* 我們的程式(mkdir,mv,touch)一定會經過System Libary,要透過他才能使用kernel的功能。
* 除非你的程式有包含驅動程式,才可以直接跟kernel做溝通。
* user的program內定System Libary都可以用,因此需要控管哪些程式只使用哪些功能,他就是seccomp,例如:passwd不需要網路的功能。
* seccomp他負責控管誰可以使用哪些功能,就算是root也可以擋掉。
* CGroup:資源運作的管控,例如:網路,記憶體,cpu。
* Capabilities:他會看你system call有沒有相對的能力,沒有能力就不給你做,例如:執行ping這個命令,要有產生封包的能力。
* SElinux/APPArmor:他會監控程式所有一切的行為,他的複雜程度相當高。他有整體性的防護系統,執行過程都會做控管。例如:網路的哪裡你不能碰,檔案的哪裡你不能碰。
> 結論:系統這麼複雜,雖然有兩道防線,但還是會被攻陷
## 檢視 mkdir System Call
strace他可以去看程式有用到哪些system call
```
$ sudo apk add strace
##-qcf用表格顯示詳細的內容 -e跟檔案有關的都列出來
##mkdir 用到三個system call
$ rm -r /tmp/x; strace -qcf -e trace=file mkdir /tmp/x
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
0.00 0.000000 0 8 3 open
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 mkdir
------ ----------- ----------- --------- --------- ----------------
100.00 0.000000 0 10 3 total
$ rm -r /tmp/x; strace -e trace=file mkdir /tmp/x
execve("/bin/mkdir", ["mkdir", "/tmp/x"], 0x7ffe34279858 /* 22 vars */) = 0
open("/etc/ld-musl-x86_64.path", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib/libacl.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
open("/lib/libattr.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
open("/lib/libgmp.so.10", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/local/lib/libgmp.so.10", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/lib/libgmp.so.10", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
open("/lib/libutmps.so.0.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
open("/lib/libskarnet.so.2.11", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
mkdir("/tmp/x", 0777) = 0
+++ exited with 0 +++
```
## Seccomp 實作範例
```
$ cd; mkdir syscall; cd syscall
##允許這個程式的system call有這些,然後會丟白名單
$ nano main.go
package main
import (
"fmt"
"syscall"
)
func main() {
var syscalls = []string{
"rt_sigaction", "mkdirat", "clone", "mmap", "readlinkat", "futex",
"rt_sigprocmask","exit_group","mprotect", "write", "sigaltstack", "gettid", "read",
"open", "close", "fstat", "munmap","brk", "access", "execve", "getrlimit",
"arch_prctl", "sched_getaffinity", "set_tid_address", "set_robust_list"}
whiteList(syscalls)
err := syscall.Mkdir("/tmp/moo", 0755)
if err != nil {
panic(err)
} else {
fmt.Printf("I just created a file\n")
}
}
```
```
##下載白名單
$ wget http://www.oc99.org/ctn/whitelist.go
##linux要呼叫seccomp,要啟動他的功能,要有他的lib
$ sudo apk add libseccomp-dev
(1/2) Installing linux-headers (5.16.7-r1)
(2/2) Installing libseccomp-dev (2.5.2-r1)
OK: 1053 MiB in 255 packages
##產生相依擋
$ go mod init mygo
##安裝 Golang 所需的 Seccomp 套件,會紀錄在mod裡面
$ go get github.com/seccomp/libseccomp-golang
go: downloading github.com/seccomp/libseccomp-golang v0.9.1
go: added github.com/seccomp/libseccomp-golang v0.9.1
$ go build -o test
$ ./test
...........
I just created a file
$ ls -al /tmp | grep moo
drwxr-xr-x 2 bigred bigred 4096 Jul 21 12:51 moo
```
更改main.go內容新增"os" 還有fmt.Printf("pid: %d\n", os.Getpid())
```go=
$ nano main.go
package main
import (
"fmt"
"syscall"
"os"
)
func main() {
........
whiteList(syscalls)
err := syscall.Mkdir("/tmp/moo", 0755)
if err != nil {
panic(err)
} else {
fmt.Printf("I just created a file\n")
}
fmt.Printf("pid: %d\n", os.Getpid())
}
```
```
$ go build -o test
$ rm -r /tmp/moo; ./test
...........
I just created a file
pid: -1
```
* 沒有允許這個程式可以去抓pid,被seccomp給限制了。
允許程式可以去呼叫getpid這個system call,把getpid放進白名單裡
```
$ nano main.go
package main
........
func main() {
var syscalls = []string{
"rt_sigaction", "mkdirat", "clone", "mmap", "readlinkat", "futex", "rt_sigprocmask",
"mprotect", "write", "sigaltstack", "open", "read","close", "fstat", "munmap",
"brk", "access", "getrlimit","exit_group","getpid"}
..........
}
##抓到pid了
$ go build -o test; rm -r /tmp/moo
$ ./test
.......
I just created a file
pid: 35701
```
chmod.json這個檔案是專門給 docker 用的
```
$ mkdir ~/seccomp; cd ~/seccomp
##system call會跟硬體跑 ##不給chmod chown chown32
$ nano chmod.json
{
"defaultAction": "SCMP_ACT_ALLOW", ##硬體規格
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"name": "chmod",
"action": "SCMP_ACT_ERRNO",
"args": []
},
{
"name": "chown",
"action": "SCMP_ACT_ERRNO",
"args": []
},
{
"name": "chown32",
"action": "SCMP_ACT_ERRNO",
"args": []
}
]
}
```
* 有打seccomp=chmod.json 就會被限制,沒打就使用預設內的;chmod.json 這個檔案是我們自己設定的限制項目;下面沒打就是使用docker 內定的 Secomp Profile
```
$ docker run --rm -it --security-opt seccomp=chmod.json quay.io/cloudwalker/alpine chmod 400 /etc/hosts
chmod: /etc/hosts: Operation not permitted
$ docker run --rm -it quay.io/cloudwalker/alpine chmod 400 /etc/hosts
```
## Linux Capabilities & Privilege Escalation
抓Capabilities的process動態資訊,$$現在執行的程式,我們現在在執行的就是/bin/bash貝殼程式
```
$ grep Cap /proc/$$/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff ##bounding,允許bigred使用者可以擁有哪些Capabilities
CapAmb: 0000000000000000
```
允許這個/bin/bash程式可以有這些Capabilities,並不代表他可以執行,要看他有沒有能力
```
$ capsh --decode=0000001fffffffff
0x0000001fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend
```
設定 Capabilities
看他有沒有可以關機的能力,這裡是syscall(他已經過了大門警衛),他會去檢查syscall有沒有可以關機的Capabilities
```
$ echo 'package main
import (
"syscall"
"log"
"fmt"
)
func main() {
fmt.Print("Press any key to continue ")
var input string
fmt.Scanln(&input)
err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
if err != nil {
log.Printf("power off failed: %v", err)
}
}' > mypoff.go
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a mypoff.go
```
執行程式
```
./mypoff
Press any key to continue
在第二個 Bigred 終端機執行以下命令,抓pid
$ pidof mypoff
130120
$ grep Cap /proc/130120/status
$ kill -9 130120
回到第一個 Bigred 終端機執行以下命令,給這個程式有重新開機的能力,+ep就是增加這個cap的能力
$ sudo setcap cap_sys_boot+ep /home/bigred/seccomp/mypoff
[註] Capabilities can also be inherited by child processes.
$ getcap /home/bigred/mypoff
/home/bigred/mypoff cap_sys_boot=ep
$ ./mypoff
Press any key to continue
```
在第二個 Bigred 終端機執行以下命令
```
$ pidof mypoff
3428
$ grep Cap /proc/3428/status
CapInh: 0000000000000000
CapPrm: 0000000000400000
CapEff: 0000000000400000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
$ capsh --decode=0000000000400000
0x0000000000400000=cap_sys_boot
$ kill -9 3428
```
```
$ echo -e "ybean\nybean" | sudo adduser -s /bin/sh -h /home/ybean ybean
Changing password for ybean
New password:
Bad password: too short
Retype password:
passwd: password for ybean changed by root
設定 rbean 家目錄中的 python3 命令檔, 具有 Capabilities setuid 權限, 代表 python3 所執行的 程式可設定 setuid 功能
$ sudo cp /usr/bin/python3 /home/ybean
$ sudo setcap cap_setuid+ep /home/ybean/python3
$ ls -al /home/ybean/python3
-rwxr-xr-x 1 root ybean 13976 Jul 10 14:12 /home/ybean/python3
$ exit
```
* 執行python3程式,他有setuid的能力
```
ybean@alp:~$ ./python3 -c 'import os,time;os.setuid(1002);time.sleep(120)'
* ybean 的 UID 是 1002, 上面命令透過 python3 的 setuid Capabilities 動態改變 python3 自己的 UID 為 ybean
ybean@alp:~$ exit
$ ssh bigred@<ALP.Podman IP>
bigred@<ALP.Docker IP>'s password: bigred
##他可以改變自己的uid
$ ps -eo user,pid,ruid,euid,cmd | grep python
ybean 9756 1002 1002 ./python3 -c import os,time;os.setuid(1002);time.sleep(120)
```
* 在ybean列出有設定 Linux capabilities 的所有命令
```
##cap_net_raw他有產生封包的能力
$ getcap -r / 2>/dev/null
/usr/sbin/fping cap_net_raw=ep
/home/ybean/python3 cap_setuid=ep
```
* 由以下命令得知 python3 命令檔並沒有設定 setuid 功能,但是他可以去執行設定uid的能力
```
$ ls -al python3
-rwxr-xr-x 1 root ybean 13976 Jul 10 14:12 python3
```
* 在以下 python3 命令所執行的 程式可執行 os.setuid(0) 這行命令, 提升 /bin/bash 這命令為 root 權限;讓這個程式有setuid capabilities讓這個程式可以逃脫,變成root,所謂的權限逃脫。
```
ybean@alp:~$ ./python3 -c 'import os;os.setuid(0);os.system("/bin/bash")'
root@alp:~$ id
uid=0(root) gid=1002(ybean) groups=1002(ybean)
root@alp:~$ ps aux | grep '/bin/bash'
root 14981 0.0 0.2 5844 4808 pts/0 S 23:27 0:00 ./python3 -c import os;os.setuid(0);os.system("/bin/bash")
root 14982 0.0 0.1 2584 2288 pts/0 S 23:27 0:00 /bin/bash
root 15004 0.0 0.0 1592 852 pts/0 S+ 23:28 0:00 grep /bin/bash
```
* 上面的 ./python3 -c import os;os.setuid(0);os.system("/bin/bash") 命令的 UID 也被改變
檢視 Docker Default Capabilities (Root)
```
##libcap讓capabilities的命令做管理
$ docker run --rm -it quay.io/cloudwalker/alpine
/ # apk add libcap
##可以看到container可以使用的Capabilities,如果docker 沒有宣告,他內部的Capabilities,就有這些;這裡沒有修改時間的Capabilities,因為如果有他會直接修改到docker host的時間
/ # capsh --print
Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=ep
........
/ # exit
```
```
$ mkdir -p ~/wulin/capimg; cd wulin
$ nano capimg/Dockerfile
FROM quay.io/cloudwalker/alpine
RUN apk update && apk add nano libcap && apk upgrade
CMD [/bin/sh]
$ docker build -t capimg capimg/
##-u 1000這個container絕對不會用root這個帳號運作,內跟外都不會;只要是root帳號會有一堆Capabilities,但如果是一般使用者,是什麼Capabilities都沒有
$ docker run --rm -u 1000 capimg capsh --print
Current: =
.......
```
container系統一樣要進大門警衛,進入之後還是要經過Capabilities的限制
```
##chown nobody / 改變他的根目錄,會成功是因為,container內定的使用者是root
$ docker run --rm capimg chown nobody /
##--cap-drop ALL 把docker給的所有Capabilities都丟掉,再把chown的Capabilities加回來,所以也是成功
$ docker run --rm -it --cap-drop ALL --cap-add CHOWN capimg chown nobody /
$ docker run --rm -it --cap-drop CHOWN capimg chown nobody /
chown: /: Operation not permitted
##把chown加回來,但使用者是nobody,因為是一般使用者,他本身就不會有任何cap,所以會噴錯
$ docker run --rm -it --cap-add chown -u nobody capimg chown nobody /
chown: /: Operation not permitted
##The above command fails because Docker does not yet support adding capabilities to non-root users.
```
# Google gVisor
* gVisor:模擬出一個輕量級的user-space kernel,可以讓container直接使用user-space kernel。如果container自己的 kernel 被破壞掉了,他並不會影響到其他container。
* 他會攔截所有application system call

* 這種container如果在產生跟卸掉就會比較慢,並且在資源上消耗會比較大,所以硬體設備一定要好,但資安就相對安全。

* google visor是由兩個程式組成的。
* linux的system call有319個。
* google有兩個program,一個是sentry(哨兵)他有55個system call來模擬211個system call,主要來模擬linux 命令。
* 只要跟檔案處理有關的,他會透過P9,來用gofar有52個system call。
* 目前有108個system call 不支援。
## 安裝 gVisor
```
##判別硬體規格
$ ARCH=$(uname -m); URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
$ wget ${URL}/runsc ${URL}/runsc.sha512 ${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
檢核 runsc 及 containerd-shim-runsc-v1 這二個執行檔
##檢查我們下載下來的有沒有被別人破壞
$ sha512sum -c runsc.sha512 -c containerd-shim-runsc-v1.sha512
runsc: OK
containerd-shim-runsc-v1: OK
##刪除檢查檔
$ rm -f *.sha512
##給rusc跟shim有執行跟讀的權限,並且把它移到環境變數的目錄區裡面
$ chmod a+rx runsc containerd-shim-runsc-v1; sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
```
## 執行 runsc
```
$ rm config.json; runsc spec
$ nano config.json
{
"ociVersion": "1.0.0",
"process": {
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sleep", "infinite"
],
```
## 設定 gVisor
```
##讓docker 知道有這個設定檔,docker 就可以使用runsc這個命令
$ sudo nano /etc/docker/daemon.json
{
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
}
}
}
$ sudo reboot
``
## 執行 gVisor
```
```
$ docker run --rm --runtime=runsc -d -p 8080:80 --name n1 quay.io/cloudwalker/nginx
$ curl -I http://localhost:8080
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Sat, 21 May 2022 11:50:28 GMT
........
##runsc-sandbox就是runcs模擬的kernel
$ ps aux | grep runsc
root 4359 0.0 0.9 736384 18972 ? Sl 21:53 0:00 runsc-gofer --root=/var/run/docker/runtime-ru ..........
runsc-sandbox.......
$ docker stop n1
```
# Docker vs Podman

> 由上圖我們可以看出 Podman 跟 Docker 的差別。Podman 沒有背景程序,它是通過 crun runtime process 直接跟 Linux 核心構通來建立容器(Container)。 buildah用來強化 Podman build。而跟 Image Registry 的溝通則加入 skopeo 專案功能。
> Podman uses a traditional fork/exec model (vs. a client/server model) for running containers.
* docker 在做 images 跟產生container 他內定都是使用root來去作業。
* root 帳號跟 deamon 都是被攻擊的對象。
podman是完全沒有 deamon,底層是 unshare linux 的老命令,他直接透過命令就可以產生container。
podman上網抓 image 都是透過 skopeo來幫podman 下載 。
他是三個專案做出來的,
## 在 Alpine Linux 安裝 Podman
```
$ sudo apk update; sudo apk upgrade
##slirp4netns 撥號網路,netavark 虛擬橋接網路,aardvark-dns 名稱解析dns。
##podman內定是用 crun
$ sudo apk add podman
(1/29) Installing conmon (2.1.2-r0)
(2/29) Installing yajl (2.1.0-r4)
(3/29) Installing crun (1.4.5-r0)
(4/29) Installing ip6tables (1.8.8-r1)
(5/29) Installing ip6tables-openrc (1.8.8-r1)
(6/29) Installing libslirp (4.7.0-r0)
(7/29) Installing slirp4netns (1.2.0-r0)
(14/29) Installing netavark (1.0.3-r0)
(15/29) Installing aardvark-dns (1.0.3-r0)
........
(28/29) Installing podman (4.1.0-r1)
.......
$ podman -v
podman version 4.1.0
$ sudo rc-update add cgroups
$ sudo rc-service cgroups start
$ sudo reboot
```
## podman 建立 Container
```
##檢查 podman 有沒有deamon
$ ps aux | grep -v grep | grep podman
##podman 可以到docker.io或quay.io去下載images
$ sudo nano /etc/containers/registries.conf
......
unqualified-search-registries = ["docker.io","quay.io"]
......
$ sudo podman run quay.io/podman/hello
........
!... Hello Podman World ...!
.--"--.
/ - - \
/ (O) (O) \
~~~| -=(,Y,)=- |
.---. /` \ |~~
~/ o o \~~~~.----. ~~
| =(X)= |~ / (O (O) \
~~~~~~~ ~| =(Y_)=- |
~~~~ ~~~| U |~~
.........
```
## 檢視 與 刪除 Container
```
$ sudo podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
16ce50f8a41e quay.io/podman/hello:latest /usr/local/bin/po... About a minute ago Exited (0) About a minute ago tender_murdock
$ sudo podman rm 16ce
16ce50f8a41e0352463f08da200b4be67cd6c626624368bb66b06b5b97ed3784
##podman 有95%的功能是跟 docker 一樣的,podman 在文章是表示rootful mode,那rootful mode,就是 docker。
$ sudo podman -> rootful mode = docker
```
## 撰寫 Dockerfile
```
$ mkdir ~/fbs
##filebrowser 網站型的檔案總管,內定命令是要使用我們自己寫的/opt/app/entrypoint 這個程式。
$ nano ~/fbs/Dockerfile
FROM quay.io/cloudwalker/alpine:latest
RUN apk --update add ca-certificates bash curl \
&& adduser -h /opt/app -D app \
&& curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
COPY entrypoint /opt/app/entrypoint
RUN chmod a+x /opt/app/entrypoint
VOLUME /srv
EXPOSE 4000
USER app
WORKDIR /opt/app
ENTRYPOINT [ "/opt/app/entrypoint" ]
```
建立與測試 alp.fbs image
```
##這個網站只能 read only
$ nano ~/fbs/entrypoint
#!/bin/bash
filebrowser config init --port 4000 --address "" --baseurl "" --log "stdout" --root="/srv" --auth.method='noauth' --commands "" --lockPassword --perm.admin=false --perm.create=false --perm.delete=false --perm.execute=false --perm.modify=false --perm.rename=false --signup=false
filebrowser users add anonymous "anonymous"
filebrowser
$ sudo podman build -t alp.fbs ~/fbs/
$ sudo podman run --name f1 -d -p 80:4000 --volume /tmp:/srv:ro alp.fbs
$ curl -I localhost/
.....
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Thu, 03 Feb 2022 13:56:12 GMT
Content-Length: 14
$ sudo podman rm -f f1
```
## Podman Rootful Container
Podman Rootful 運作架構
```
##把sudo podman 改名成 docker
$ alias docker='sudo podman'
$ docker run --name a1 -it -d quay.io/cloudwalker/alpine sh
fc115717b17c4288b3618368f216d5fa5c170e7b08bcd3ac26fd1a5cb3717b01
##podman 做的container,也是root去產生的
$ ps aux | grep ' sh$'
root 12629 0.0 0.0 1684 944 pts/0 Ss+ 22:03 0:00 sh
##podman 做的container裡面內定的帳號也是root
$ docker exec -it a1 whoami
root
$ docker rm -f a1
```
## Podman Rootful Container Network
```
##一次產生兩個container
$ docker run --name b1 -itd quay.io/cloudwalker/alpine sh; docker run --name b2 -itd quay.io/cloudwalker/alpine sh
$ docker exec b1 hostname -i; docker exec b2 hostname -i
10.88.0.7
10.88.0.8
##兩個container的網路是互相連通的
$ docker exec b2 ping -c 2 10.88.0.7
PING 10.88.0.7 (10.88.0.7): 56 data bytes
64 bytes from 10.88.0.7: seq=0 ttl=42 time=1.472 ms
64 bytes from 10.88.0.7: seq=1 ttl=42 time=0.152 ms
$ docker exec b2 ping -c 2 b1
ping: bad address 'b1'
##container裡面的內定的dns也是跟docker host 一樣
$ docker exec b1 cat /etc/resolv.conf
search localdomain
nameserver 192.168.188.2
$ docker rm -f b1 b2
```
## Rootful Container 自訂網路架構
```
$ brctl show
bridge name bridge id STP enabled interfaces
podman0 8000.86e232192730 no
$ ifconfig podman0
podman0 Link encap:Ethernet HWaddr 2A:1F:75:70:E2:54
inet addr:10.88.0.1 Bcast:10.88.255.255 Mask:255.255.0.0
..........
$ docker network create --driver=bridge --subnet=192.168.166.0/24 --gateway=192.168.166.254 mynet
第一次建立自建的 Podman 網路, 不會建立 虛擬橋接器, 需產生連接 mynet 的 Container 才會產生 虛擬橋接器
$ brctl show
bridge name bridge id STP enabled interfaces
podman0 8000.86e232192730 no
```
在podman自建網路,可以自己固定ip,也可以使用dns,
```
$ docker run --rm --net mynet --ip=192.168.166.3 --name c1 -h cg61 -d alpine sleep 360
$ docker run --rm --net mynet --ip=192.168.166.4 --name c2 -h cg62 -d alpine sleep 360
$ brctl show
bridge name bridge id STP enabled interfaces
podman0 8000.4e7b314d9b71 no
podman1 8000.f695873fdba7 no veth0e1577bc
vethdef97e4b
$ docker exec c2 ping -c 2 192.168.166.3
PING 192.168.166.6 (192.168.166.6): 56 data bytes
64 bytes from 192.168.166.6: seq=0 ttl=42 time=0.083 ms
64 bytes from 192.168.166.6: seq=1 ttl=42 time=0.125 ms
$ docker exec c2 ping -c 2 cg61
ping: bad address 'cg61'
$ docker stop c1 c2
```
## Rootful Macvlan 網路
```
$ docker network create -d macvlan -o parent=eth0 --subnet 192.168.61.0/24 newnet
* Create a Macvlan based network using the host interface eth0. Macvlan networks can only be used as root.
##創建一張網卡
$ docker network ls
NETWORK ID NAME DRIVER
5d517fc03050 mynet bridge
5b828fdc0811 newnet macvlan
2f259bab93aa podman bridge
$ docker run --rm --net newnet --name c3 -h lcs12 -d --ip=192.168.61.222 quay.io/cloudwalker/alpine sleep 360
$ docker exec c3 hostname -i
192.168.61.222
$ docker exec c3 ping -c 2 192.168.61.1
PING 192.168.61.1 (192.168.61.1): 56 data bytes
64 bytes from 192.168.61.1: seq=0 ttl=42 time=0.171 ms
64 bytes from 192.168.61.1: seq=1 ttl=42 time=0.543 ms
```
## Application Container 記憶體管理
規範 container 的運算資源
```
$ docker run --rm --name c1 -d -it quay.io/cloudwalker/alpine
##查看現在 container 有使用多少資源
$ docker stats c1
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
982a40d8711f c1 -- 1.446MB / 2.054GB 0.07% 726B / 54B -- / -- 1 4.084301ms 0.01%
按 Ctrl + C 停止監視
$ docker stop c1
##限制 c1 最多只能使用 300m 記憶體
$ docker run --rm --name c1 -m 300m -d -it quay.io/cloudwalker/alpine
$ docker stats c1
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
7883c220aa4f c1 0.00% 1.137MiB / 300MiB 0.38% 946B / 0B 0B / 0B 1
^C
$ docker stop c1
```
## Application Container CPU 管理
新增二個 Container, 均指定使用第二核心, c1 設定使用 20% CPU, c2 設定使用 30% CPU
```
$ docker run --rm --name c1 --cpuset-cpus="1" --cpus="0.2" -itd quay.io/quay/busybox yes
$ docker run --rm --name c2 --cpuset-cpus="1" --cpus="0.3" -itd quay.io/quay/busybox yes
$ docker stats c1 c2
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
18f8dc0418e5 c1 19.82% 472KiB / 5.306GiB 0.01% 0B / 0B 0B / 0B 1
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
24f07c022cc0 c2 31.11% 500KiB / 5.306GiB 0.01% 0B / 0B 0B / 0B 1
^C
$ docker stop c1 c2
```
## Podman Container 帳號
破壞 Alpine Container
```
$ docker run --rm -it quay.io/cloudwalker/alpine sh
/ # which rm
/bin/rm
/ # echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
/ # rm -r -f /* &>/dev/null
/ # ls -al /
sh: ls: not found
/ # exit
```
## 指定 Container 執行的帳號
取得 ALP 的 bigred 帳號 ID
```
$ echo $UID
1000
```
指定用 bigred 帳號執行以下 Container
`$ docker run --rm -it -d --name u1 --user 1000 quay.io/cloudwalker/alpine`
```
$ ps aux | grep " /bin/sh"
bigred 86766 0.0 0.0 1692 940 pts/0 Ss+ 21:57 0:00 sh
```
```
$ docker exec -it u1 sh
/ $ whoami
whoami: unknown uid 1000
[註] 因 1000 這 UID, 在 Container 系統中找不到, 所以你會看到 "unknown uid 1000", 以致於許多 Linux 命令無法執行, 例如 passwd
/ $ rm -rf / &>/dev/null
/ $ ls /
bin etc lib mnt proc run srv tmp var
dev home media opt root sbin sys usr
/ $ sudo reboot
sh: sudo: not found
/ $ exit
$ docker stop u1
```
## Rootless Container 運作架構
```
$ podman run -it quay.io/cloudwalker/alpine
ERRO[0000] cannot find UID/GID for user bigred: No subuid ranges found for user "bigred" in /etc/subuid - check rootless mode in man pages.
WARN[0000] Using rootless single mapping into the namespace. This might break some images. Check /etc/subuid and /etc/subgid for adding sub*ids if not using a network user
Trying to pull quay.io/cloudwalker/alpine:latest...
Getting image source signatures
Copying blob 540db60ca938 done
Error: writing blob: adding layer with blob "sha256:540db60ca9383eac9e418f78490994d0af424aab7bf6d0e47ac8ed4e2e9bcbba": Error processing tar file(exit status 1): potentially insufficient UIDs or GIDs available in user namespace (requested 0:42 for /etc/shadow): Check /etc/subuid and /etc/subgid if configured locally and run podman-system-migrate: lchown /etc/shadow: invalid argument
```
在 ALP.Podman 終端機, 建立 rbean 及 gbean 帳號
```
$ echo -e "rbean\nrbean" | sudo adduser -s /bin/sh -h /home/rbean rbean
$ echo -e "gbean\ngbean" | sudo adduser -s /bin/sh -h /home/gbean gbean
##規劃等一下bigred會還需要額外的uid,rbean跟gbean 一樣。
##bigred uid從100000開始,可以使用六萬多個 ,rbean 從200000 也有六萬多個,gbean以此類推。
##這邊是uid的範圍,不是uid的數字
$ echo 'bigred:100000:65535
rbean:200000:65535
gbean:300000:65535' | sudo tee /etc/subuid
$ echo 'bigred:100000:65535
rbean:200000:65535
gbean:300000:65535' | sudo tee /etc/subgid
```
一定要重新開機, /etc/subuid 及 /etc/subgid 的設定才會生效
`$ sudo reboot`
使用 rbean 帳號登入
* 不同使用者可以有各自的 podman 運作架構
```
$ ssh rbean@<ALP.Podman IP>
rbean@172.29.0.55's password: rbean
##這個container只會在rbean,在bigred看不到
$ podman run --name p1 -d quay.io/cloudwalker/alpine sleep infinite
##建立container的是rbean
$ ps aux | grep 'sleep infinite'
rbean 55795 0.0 0.0 1588 4 ? Ss 12:34 0:00 sleep infinite
##container裡面的使用者,一樣內定都還是root
$ podman exec p1 id
uid=0(root) gid=0(root)
$ podman rm -f p1
```
在rbean 家目錄裡可以看到 container 的完整檔案目錄內容。
```
$ tree -L 4 .local/
.local/
└── share
└── containers
├── cache
│ └── blob-info-cache-v1.boltdb
└── storage
├── defaultNetworkBackend
├── libpod
├── mounts
├── networks
├── overlay
├── overlay-containers
├── overlay-images
├── overlay-layers
├── storage.lock
├── tmp
└── userns.lock
```
##keep-id container 內跟外的使用者都相同
```
$ podman run --rm --name u1 --userns keep-id -d alpine sleep 120
$ ps aux | grep 'sleep 120'
rbean 7274 0.0 0.0 1588 4 ? Ss 23:55 0:00 sleep 120
rbean 7306 0.0 0.0 1588 4 pts/0 R+ 23:55 0:00 grep sleep 120
$ podman exec -it u1 id
uid=1002(rbean) gid=1002(rbean)
$ podman stop u1
```
指定 uid 999
```
$ podman run --rm --name u1 -u 999 -d alpine sleep 360
##uid要加上我們剛剛自己設定的200000,因為是從0開始算,所以是200998。
$ ps aux | grep sleep
200998 5326 0.0 0.0 1584 4 ? Ss 22:38 0:00 sleep 360
rbean 5340 0.0 0.0 1584 888 pts/0 S+ 22:38 0:00 grep sleep
[註] 在 /etc/subuid 設定檔中, 設定 rbean 可選用的 UID 從 200000 開始, 而 -u 參數設定 999, 這時在 Podman Host 執行 u1 Container 命令 (sleep 360) 的 UID 為 200998
##在container內部就是我們自己指定的999
$ podman exec -it u1 id
uid=999(999) gid=0(root)
[註] 在 u1 Container 中執行的 UID, 便是根據 -u 參數指定的 UID 來執行
```
## Podman Rootless image
rbean 建立名為 myuser 的 Dockerfile
```
$ echo '
FROM quay.io/cloudwalker/ubuntu
RUN groupadd appgroup
RUN useradd -r -u 1005 -g appgroup appuser
USER appuser
ENTRYPOINT ["sleep", "infinity"]' > myuser
## "-" 減號代表標準輸入,"<" 會把myuser的檔案丟到podman build來製作出image,所以製作Dockerfile,不一定使取名Dockerfile
$ podman rmi myuser; podman build -t myuser - < myuser
$ podman run --rm --name u1 -d myuser
$ ps aux | grep sleep
201004 5577 0.0 0.0 2516 592 ? Ss 23:25 0:00 sleep infinity
$ podman exec u1 id
uid=1005(appuser) gid=1000(appgroup) groups=1000(appgroup)
```
> 問題:gbean 使用者可否使用 rbean 自製的 image ?
> 答:可以的,利用image的備份跟還原。
```
##把rbean做的 image 把他打包
$ podman save base.img:v1.0 > base.img.tar
$ exit
$ ssh gbean@172.16.119.2
gbean@172.16.119.2's password: gbean
$ podman images
REPOSITORY TAG
IMAGE ID CREATED SIZE
##把打包擋從 rbean 複製到 gbean
$ cp /home/rbean/base.img.tar .
##把打包擋還原成 image
$ podman load < base.img.tar
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/base.img v1.0 d4e63a3c44c7 3 hours ago 820 MB
$ podman run --rm -it base.img
* Starting OpenBSD Secure Shell server sshd [ OK ]
bigred@5f13ae2cdf56:~$ exit
```
## Podman Rootless Container Network

* slirp:撥號網路,傳統網路,他有辦法做到在linux在使用者的環境可以透過他來上網。
* 在podman host 啟動slirp4netns程式,透過這個程式他會在container裡面啟動 tap0的虛擬網路卡,然後他會連接podman host的實體網卡,最後達成上網的功能。
## 實做 slirp4netns
安裝 Podman 會一併安裝 slirp4netns 這套件
```
$ slirp4netns -v
slirp4netns version 1.2.0
commit: 656041d45cfca7a4176f6b7eed9e4fe6c11e8383
libslirp: 4.7.0
SLIRP_CONFIG_VERSION_MAX: 4
libseccomp: 2.5.2
$ sudo unshare --pid --fork --mount-proc --net --uts sh
root@alp:/home/bigred$ ifconfig -a
lo: flags=8<LOOPBACK> mtu 65536
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
```
再開啟一個的 CMD 視窗, 執行以下命令
`$ ssh bigred@<alp.docker IP>`
```
$ ps aux | grep ' sh$'
18674 root 0:00 unshare --pid --fork --mount-proc --net --uts sh
18676 root 0:00 sh
##linux的核心模組,tun是產生網路卡的命令
$ sudo modprobe tun
##把tap0網路卡加到18676這個process裡面去
##--mtu=65520 設程65520,上網的封包傳輸速度可以高達 1g 左右,並且他不會斷線
$ sudo slirp4netns --configure --disable-host-loopback --mtu=65520 18676 tap0
........
* MTU: 65520
* Network: 10.0.2.0
* Netmask: 255.255.255.0
* Gateway: 10.0.2.2
* DNS: 10.0.2.3
* Recommended IP: 10.0.2.100
[註] 按 ctrl + c 可停止上面命令執行, unshare 終端機中的 tap0 網卡會自動移除
```
Rootless Container 內定網路架構
```
$ podman run --rm -it quay.io/cloudwalker/alpine
##podman 產生的container網卡名一定是tap0,tap0的網卡就是slirp產生的。
/ # ifconfig tap0
tap0 Link encap:Ethernet HWaddr 4A:5A:DB:06:3F:C7
inet addr:10.0.2.100 Bcast:10.0.2.255 Mask:255.255.255.0
.........
/ # route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.2.2 0.0.0.0 UG 0 0 0 tap0
10.0.2.0 0.0.0.0 255.255.255.0 U 0 0 0 tap0
##裡面的ip都是10.xx.xx.xx開頭的
/ # cat /etc/resolv.conf
nameserver 10.0.2.3
nameserver 172.16.119.1
/ # ping -c 1 www.hinet.net
PING www.hinet.net (61.221.82.5): 56 data bytes
64 bytes from 61.221.82.5: seq=0 ttl=42 time=24.640 ms
/ # exit
```
```
$ podman run --rm -itd --name a1 quay.io/cloudwalker/alpine; podman run --rm -itd --name a2 quay.io/cloudwalker/alpine
$ podman exec a1 hostname -i; podman exec a2 hostname -i
10.0.2.100
10.0.2.100
* a1 與 a2 的 IP 位址一樣, 他們網路無法互通
$ ps aux | grep -v grep | grep -e "^rbean.*slirp"
rbean 6147 0.0 0.0 2740 1600 pts/0 S 01:21 0:00 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable-sandbox --enable-seccomp -c -e 3 -r 4 --netns-type=path /tmp/podman-run-1002/netns/cni-d2dd84cb-26e0-4f6b-1079-f589fe538949 tap0
rbean 6182 0.0 0.0 2740 1608 pts/0 S 01:21 0:00 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable-sandbox --enable-seccomp -c -e 3 -r 4 --netns-type=path /tmp/podman-run-1002/netns/cni-3cf24bd7-f8aa-ff26-6a35-3e8d9d57bfbc tap0
$ podman rm -f a1 a2
```

因為有兩個container,所以他們有各自的slirp這個程式去幫他們做網路的服務,他們的ip都是10.0.2.100
```
$ mkdir html ; echo "<h1>Rootless Container</h1>" > html/index.html
##host date volume ,--publish 8080:80
$ podman run --rm -d --name n1 --publish 8080:80 --volume ${PWD}/html:/usr/share/nginx/html quay.io/cloudwalker/nginx
✔ docker.io/library/nginx:latest
Trying to pull docker.io/library/nginx:latest...
.........
5d6265214ef71ffce826d38b16dffd2557a0cfcd353dbecef92b87d4a6d47071
$ curl http://localhost:8080
<h1>Rootless Container</h1>
$ ps aux | grep -v grep | grep nginx
rbean 10709 0.0 0.0 10632 5732 ? Ss 21:51 0:00 nginx: master process nginx
-g daemon off;
200100 10726 0.0 0.0 11024 2512 ? S 21:51 0:00 nginx: worker process
```

使用各自的port可以讓兩台相同ip的container互相連到對方。
```
$ podman run --name b1 -itd quay.io/cloudwalker/alpine.sshd
$ podman exec b1 netstat -an | grep 22
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp6 0 0 :::22 :::* LISTEN
##這邊是不可能直接連進去的,一定要走對應的port
$ ssh bigred@10.0.2.100
^C
$ podman rm -f b1
##在podman host開啟對應的port 22100
$ podman run --name b1 -h ssn763 -p 22100:22 -itd quay.io/cloudwalker/alpine.sshd
$ ssh bigred@localhost -p 22100
Warning: Permanently added '[localhost]:22100' (ED25519) to the list of known hosts.
bigred@localhost's password: bigred
bigred@ssn763:~$ exit
```

rootless的container,我在podman host是不可能直接使用ssh 連進到我的container,一定要連進對應的port才能順利連進去。
## Rootless Container 自建網路架構
```
##rootless 的網路自建
$ podman network create --subnet=192.168.188.0/24 --gateway=192.168.188.254 mynet2
$ podman network ls
NETWORK ID NAME DRIVER
c26de056b034 mynet2 bridge
2f259bab93aa podman bridge
```
```
$ podman run --name n1 --net mynet2 -itd quay.io/cloudwalker/alpine; podman run --name n2 --net mynet2 -itd quay.io/cloudwalker/alpine
$ podman exec n1 hostname -i; podman exec n2 hostname -i
192.168.188.2
192.168.188.3
##只有一個slirp程式在跑,代表這兩個container都連到這個程式,再透過他上網,所以這個程式有自帶switch的功能。
$ ps aux | grep slirp
bigred 13570 0.0 0.0 2816 1584 pts/1 S 15:40 0:00 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable-sandbox --enable-seccomp --enable-ipv6 -c -r 3 --netns-type=path /tmp/podman-run-1000/netns/rootless-netns-076c75c65edc8a6438d9 tap0
$ podman exec n2 ping -c2 192.168.188.2
PING 192.168.188.4 (192.168.188.2): 56 data bytes
64 bytes from 192.168.188.2: seq=0 ttl=42 time=0.029 ms
64 bytes from 192.168.188.2: seq=1 ttl=42 time=0.096 ms
$ podman exec n2 ping -c2 n1
PING n1 (192.168.188.1): 56 data bytes
64 bytes from 192.168.188.1: seq=0 ttl=42 time=0.033 ms
64 bytes from 192.168.188.1: seq=1 ttl=42 time=0.153 ms
```
## Podman Pods

podman裡面放的是一個解決方案。
## 建立 Pod
```
$ podman pod create -n mypod
##他現在狀態是create,因為還沒有其他的container加進來
$ podman pod list
POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
6ba66538a764 mypod Created 54 seconds ago 22e55cf8d3ec 1
##k8s.gcr.io/pause:4.1.0 是 google 給的 image,建立了一個container叫 infra,是利用k8s.gcr.io/pause:4.1.0這個image
##他裡面跑的process是sleep,因為這個命令沒有用到硬碟,網路,只有用到一點點的記憶體和cpu,所以別人要攻陷他很困難,因此我們可以把很多運算資源,一堆的 (network namespace,pid namespace...等)全部塞到這個人身上。
$ podman ps -a --pod
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES POD ID PODNAME
.......
5f9122efabfb k8s.gcr.io/pause:4.1.0 36 seconds ago Created 33ee073adce4-infra 33ee073adce4 mypod
```
## Add a container to a pod
```
##ap1這個container加入到pod裡面
$ podman run -d --pod mypod --name ap1 quay.io/cloudwalker/alpine top
##現在有兩個pod
$ podman ps -a --pod | grep mypod
22e55cf8d3ec k8s.gcr.io/pause:3.5 15 minutes ago Up 3 minutes ago 6ba66538a764-infra 6ba66538a764 mypod
cdcb45130dae docker.io/library/alpine:latest top 3 minutes ago Up 3 minutes ago nervous_ardinghelli 6ba66538a764 mypod
##再把一個網站加入到pod
$ podman run -d --pod mypod --name np1 quay.io/cloudwalker/nginx
$ podman exec ap1 hostname
mypod
$ podman exec np1 hostname
mypod
$ podman exec ap1 hostname -i
10.0.2.100
$ podman exec np1 hostname -i
10.0.2.100
```
pod裡面的container的網路是會直些連到,不需要透過ip,在pod裡面的所有container都共通同一片網路卡
```
$ podman exec -it ap1 sh
/ # netstat -anp | head -n 4
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 :::80 :::* LISTEN -
/ # apk add curl
/ # curl http://localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
..........
/ # exit
```
## Generate a K8S YAML File(給k8s要用的yaml檔)
```
##把mypod這個pod變成,k8s要用的yaml檔
$ podman generate kube mypod -f mypod.yaml
##刪除pod
$ podman pod rm -f mypod
##查看yaml檔
$ cat mypod.yaml
..............
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2021-09-30T16:41:21Z"
labels:
app: mypod
name: mypod
spec:
containers:
- command:
- top
...............
```
```
$ podman ps -a --pod
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES POD ID PODNAME
##剛剛刪除的pod直接還原,並且pod直接running
$ podman play kube mypod.yaml
$ podman ps --pod
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES POD ID PODNAME
693f72086244 localhost/podman-pause:4.1.0-1658428998 16 seconds ago Up 11 seconds ago 3445273f88b5-infra 3445273f88b5 mypod
9379259cf770 quay.io/cloudwalker/alpine:latest 13 seconds ago Up 11 seconds ago mypod-ap1 3445273f88b5 mypod
00c772c2b317 quay.io/cloudwalker/nginx:latest nginx -g daemon o... 10 seconds ago Up 11 seconds ago mypod-np1 3445273f88b5 mypod
$ podman pod rm -f mypod
```
## 單命令建立 Pod Application
```
##-e 環境變數; 把mariadb加入到wpapp_pod這個pod
##這邊設定pod的port是80,因為所有container共用同一片網路卡,所以這邊設定80就可以了
$ podman run -d --restart=always --pod new:wpapp_pod -e MYSQL_ROOT_PASSWORD="myrootpass" -e MYSQL_DATABASE="wp-db" -e MYSQL_USER="wp-user" -e MYSQL_PASSWORD="w0rdpr3ss" -p 8080:80 --name=wptest-db quay.io/cloudwalker/mariadb
* The use of new: indicates to Podman that we want to create a new pod rather than attempt to assign the container to an existing pod. --name becomes name of the container inside pod
檢視 wpapp_pod 結構
$ podman ps -a --pod | grep wpapp_pod
ed33568282b5 k8s.gcr.io/pause:3.5 7 minutes ago Up 7 minutes ago a9b966b06330-infra a9b966b06330 wpapp_pod
ed13da140792 docker.io/library/mariadb:latest mysqld 7 minutes ago Up 7 minutes ago wptest-db a9b966b06330 wpapp_pod
```
## wpapp_pod 加入 Wordpress Container
```
##需要網站才能使用mariadb,mariadb ip 127.0.0.1
$ podman run -d --restart=always --pod=wpapp_pod -e WORDPRESS_DB_NAME="wp-db" -e WORDPRESS_DB_USER="wp-user" -e WORDPRESS_DB_PASSWORD="w0rdpr3ss" -e WORDPRESS_DB_HOST="127.0.0.1" --name wptest-web quay.io/cloudwalker/wordpress
$ podman ps -a --pod | grep wpapp_pod
d03a58456602 k8s.gcr.io/pause:3.5 About a minute ago Up About a minute ago 0.0.0.0:8080->80/tcp 962c315aea34-infra 962c315aea34 wpapp_pod
86b9745759a6 docker.io/library/mariadb:latest mysqld About a minute ago Up About a minute ago 0.0.0.0:8080->80/tcp wptest-db 962c315aea34 wpapp_pod
22cf7b61b3ab docker.io/library/wordpress:latest apache2-foregroun... 14 seconds ago Up 14 seconds ago 0.0.0.0:8080->80/tcp wptest-web 962c315aea34 wpapp_pod
```
連接 Wordpress 網站
```
$ curl http://localhost:8080/wp-admin/install.php
.........
</head>
<body class="wp-core-ui language-chooser">
<p id="logo">WordPress</p>
.......
##刪除pod
$ podman pod stop wpapp_pod
$ podman pod rm wpapp_pod
```
###### tags: `系統工程`