# Podman
###### tags: `container`, `Podman`
目前比較多人用的是 [docker](https://www.docker.com/) 工具來進行 container 管理,但我想用 [Podman](https://podman.io/)。原因是因為 Open Source,且 Podman 是 [Open Container Initiative](https://opencontainers.org/) (OCI) 提供的工具。
有能力的話,可以自己加功能、修 Bug,並送回 upstream。但我還不會 [Go](https://golang.org/) 就是了 :P
* Podman 和 Docker 基本概念、指令可以互通使用
* 可以使用相同的 image registry,例如 [docker.io/library](https://hub.docker.com/)
* Docker 要先用 systemctl 起 docker.service,再下指令;但 Podman 直接是指令 base,可以直接下指令使用。
## 安裝
```shell=
pacman -S podman
```
## Pull image
```shell=
$ podman pull docker.io/library/alpine:3.14
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/alpine 3.14 021b3423115f 7 days ago 5.87 MB
```
## Have the shell in a container
```shell=
$ podman run -it --name test docker.io/library/alpine:3.14 sh
/ #
$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
649b5756f07f docker.io/library/alpine:3.14 sh 25 seconds ago Exited (0) 24 seconds ago test
$ podman start test
test
$ podman attach test
/ #
```
## Build an image with Dockerfile
```shell=
$ cat python-dockerfile
FROM docker.io/library/alpine:3.14
RUN apk add --no-cache python3
$ podman build -t mypython -f python-dockerfile
STEP 1: FROM docker.io/library/alpine:3.14
STEP 2: RUN apk add python3 --no-cache
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/x86_64/APKINDEX.tar.gz
(1/13) Installing libbz2 (1.0.8-r1)
(2/13) Installing expat (2.4.1-r0)
(3/13) Installing libffi (3.3-r2)
(4/13) Installing gdbm (1.19-r0)
(5/13) Installing xz-libs (5.2.5-r0)
(6/13) Installing libgcc (10.3.1_git20210424-r2)
(7/13) Installing libstdc++ (10.3.1_git20210424-r2)
(8/13) Installing mpdecimal (2.5.1-r1)
(9/13) Installing ncurses-terminfo-base (6.2_p20210612-r0)
(10/13) Installing ncurses-libs (6.2_p20210612-r0)
(11/13) Installing readline (8.1.0-r0)
(12/13) Installing sqlite-libs (3.35.5-r0)
(13/13) Installing python3 (3.9.5-r1)
Executing busybox-1.33.1-r3.trigger
OK: 56 MiB in 27 packages
STEP 3: COMMIT mypython
--> 20332d09dbe
Successfully tagged localhost/mypython:latest
20332d09dbe5b483818a106bf5e72bceb478c2274686db476bb3097f0d8a0895
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/mypython latest 20332d09dbe5 58 seconds ago 54.2 MB
docker.io/library/python 3-alpine3.14 d4d6be1b90ec 34 hours ago 48 MB
docker.io/library/alpine 3.14 021b3423115f 7 days ago 5.87 MB
```
有趣的是,發現自己用 alpine 當 base 裝 python3,最後 build 出來的 image 比直接從 dockerhub 上拉一樣版本的 Python image [python:3-alpine3.14](https://hub.docker.com/_/python) 還要肥大!當然,也不是所有的 case 都是這樣,但至少 Python image 是這樣。
## Add and execute a Python application in a container directly
```shell=
$ cat pyhello-dockerfile
FROM docker.io/library/python:3-alpine3.14
COPY hello.py /usr/bin/
$ cat hello.py
#!/usr/bin/env python3
print("Hello World!")
$ chmod +x hello.py
$ podman build -t python-hello -f pyhello-dockerfile
STEP 1: FROM docker.io/library/python:3-alpine3.14
STEP 2: COPY hello.py /usr/bin/
STEP 3: COMMIT python-hello
--> 4d28af87192
Successfully tagged localhost/python-hello:latest
4d28af87192c0d1940d615c0b9a8b9a82590fd4181b8dd0e9ca5bb1a7ea43b7f
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/python-hello latest 4d28af87192c 9 seconds ago 48 MB
docker.io/library/python 3-alpine3.14 d4d6be1b90ec 34 hours ago 48 MB
docker.io/library/alpine 3.14 021b3423115f 7 days ago 5.87 MB
$ podman run --name pyhello python-hello hello.py
Hello World!
```
## Have a bottle HTTP server in a container
Reference: [Running Bottle Apps in Docker Containers on macOS](https://www.fullstackpython.com/blog/first-steps-bottle-web-apps-docker-containers.html)
```shell=
$ ls app/
app.py requirements.txt
$ cat app/requirements.txt
bottle
$ cat app/app.py
#!/usr/bin/env python3
from bottle import route, run
@route('/')
def hello_world():
return "Hello, world! (From Full Stack Python)"
if __name__ == "__main__":
run(host="0.0.0.0", port=8080, debug=True, reloader=True)
$ chmod +x app/app.py
$ cat bottle-dockerfile
FROM docker.io/library/python:3-alpine3.14
WORKDIR /app
COPY app /app
RUN pip install --no-cache-dir -r requirements.txt
$ podman build -t mybottle -f bottle-dockerfile
STEP 1: FROM docker.io/library/python:3-alpine3.14
STEP 2: WORKDIR /app
--> 4b04293273b
STEP 3: COPY app /app
--> fb59b39cb01
STEP 4: RUN pip install --no-cache-dir -r requirements.txt
Collecting bottle
Downloading bottle-0.12.19-py3-none-any.whl (89 kB)
Installing collected packages: bottle
Successfully installed bottle-0.12.19
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
STEP 5: COMMIT mybottle
--> fe155ca85e1
Successfully tagged localhost/mybottle:latest
fe155ca85e13658653ab20d29f4c4b52558c39919c656ad37a4b372edd063d5b
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/mybottle latest fe155ca85e13 About a minute ago 55 MB
docker.io/library/python 3-alpine3.14 d4d6be1b90ec 34 hours ago 48 MB
docker.io/library/alpine 3.14 021b3423115f 7 days ago 5.87 MB
```
測試一下,要導 port 進 container:
```shell=
$ podman run -p 5000:8080 --name testbottle mybottle /app/app.py
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.
10.0.2.100 - - [14/Aug/2021 07:55:25] "GET / HTTP/1.1" 200 38
```
另一個 terminal
```shell=
$ curl localhost:5000
Hello, world! (From Full Stack Python)
```
也可把 container 起成像是 keep alive standalone daemon:
```shell=
$ podman run -d -p 5000:8080 --name testbottle mybottle /app/app.py
76f1bc5f3a31a2cef4d1d3a751d01cee04ab55abecb65c09a8a046600898b47e
$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
76f1bc5f3a31 localhost/mybottle:latest /app/app.py 8 seconds ago Up 8 seconds ago 0.0.0.0:5000->8080/tcp testbottle
# Stop the container
$ podman stop testbottle
testbottle
$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
76f1bc5f3a31 localhost/mybottle:latest /app/app.py 3 minutes ago Exited (137) 45 seconds ago 0.0.0.0:5000->8080/tcp testbottle
```
可以看 standalone container 裡 process 的 log:
```shell=
$ podman logs mybottle
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.
10.0.2.100 - - [14/Aug/2021 14:43:14] "GET / HTTP/1.1" 200 38
```
也可以另外連進 standalone container,並取得、執行 shell
```shell=
$ podman exec -it mybottle sh
/app #
```
## Secrets
Container 裡的程式有時會需要一些敏感資料,如帳號、密碼。可以用 `-e --env` 設定 container 環境變數的方式,pass 一些參數。這可以搭配 Continuous Delivery (CD) 服務的 secret store。
```shell=
$ podman run --rm -it -e ENVTEST=iamtest alpine sh
/ # echo $ENVTEST
iamtest
```
另外,也可以用一些 Container 管裡工具的 secret store 功能,如 Podman、docker、[Kubernetes](https://kubernetes.io/) 和 cloud service provider 都有提供類似功能。
* [Podman secret command](http://docs.podman.io/en/latest/secret.html)
* [Podman - Secrets](https://blog.while-true-do.io/podman-secrets/)
以 Podman 為例,可以用 `--secret` pass secret object 進 container,Podman 會在 container 裡 mount 該 secret object 為一個 file `/run/secrets/<secret object's name>`:
```shell=
$ echo "iamsensitive" | podman secret create SecureData -
$ podman secret inspect SecureData
[
{
"ID": "ade70aaccf022dd93d039f1df",
"CreatedAt": "2021-08-14T23:16:28.261537528+08:00",
"UpdatedAt": "2021-08-14T23:16:28.261537528+08:00",
"Spec": {
"Name": "SecureData",
"Driver": {
"Name": "file",
"Options": null
}
}
}
]
$ podman run --rm --secret SecureData alpine cat /run/secrets/SecureData
iamsensitive
```
## Bind mount a volume into the container
利用 volume 的機制,把 host 上的 folder 掛進 container 裡:
```shell=
$ mkdir /tmp/data
$ date | tee /tmp/data/mydata
Sun Aug 15 11:55:01 AM CST 2021
$ podman run --rm -it -v /tmp/data:/home/dev alpine sh
/ # ls -l /home/dev/
total 4
-rw-r--r-- 1 root root 32 Aug 15 03:55 mydata
/ # cat /home/dev/mydata
Sun Aug 15 11:55:01 AM CST 2021
/ #
```
## Reference
* MariaDB 在 dockerhub 上有很完整的[教學範例](https://hub.docker.com/_/mariadb) (以 docker 為例)