Linux伺服器 Linux伺服器

資工所專業課程上課教材

資工所專業課程 > 課程內容 > 專題 - 使用 podman 進行 container 管理

專題 - 使用 podman 進行 container 管理

上次更新日期 2021/12/21

docker 是容器管理的最早企劃之一,所以很多的管理大多以 docker 為依據來進行的。但是, docker 必須要使用 root 的身份去啟動與管理,對於某些特別注意資安的朋友來說,感覺就是怕怕的。 因此,Red Hat 推出了 podman 搭配 runc 來進行輕量級虛擬化,也就是容器的管理與運行! 這玩意兒挺有趣的,透過一般帳號就可以來運作與管理容器,更有趣的是,這傢伙可以透過 systemd 來管理! 因此,玩了 podman 連 systemd 都有進一步的理解!相當有趣喔!

什麼是 podman 與安裝 container-tools

  1. 容器三要件:
    1. 基本上,無論是 docker、podman 等,都是用來管理容器 (container) 的相關軟體,而容器的運作, 大多離不開倉儲 (registry)、映像檔 (image)、容器 (container) 三者,這三個的關係已經在 docker 章節講過一次, 這裡再次強調一下!你得要回去前面的章節好好瞧一瞧喔!
    2. 映像檔 (image):
      容器都是從映像檔喚起的,所以這裡的『映像檔 (image)』並不只是 VM 的硬碟而已,所謂的映像檔包含了: 系統函式庫、指令執行檔、相關編譯函式庫、各種服務配置檔案、啟動腳本與配置等。另外,映像檔是不能更改的! 映像檔的打包需要依循 OCI (Open Container Initiative image) 規格來設計才行。
    3. 服務使用容器來設計的概念:
      基本上,過去我們學的底層伺服器,大多是一部主機啟用之後,在上面啟動多個服務,針對不同的需求來設計。 新的依據容器的設計理念,則是任何服務都使用單一的容器去啟動,然後透過不同容器間的介接來達成服務間的溝通。 由於所有的服務都是獨立的,因此,任何一個服務出問題時,並不會影響到其他服務的運作,當然你的主機就會活的比較好。 另外,也由於所有的服務都在獨立的容器上,所以搬移時,就會顯的非常簡單。
  2. podman 是什麼?
    1. podman 是一套 Red Hat 提供的容器管理工具組,這個工具組包含了數個工具:
      • podman:可以用來管理映像檔與容器本身
      • skopeo:用來檢查、複製、刪除與簽核映像檔
      • buildah:用來建立新的映像檔
    2. podman 可以在非 root 環境下執行
      前面提到的 docker 得要使用 root 的身份去管理,所以所有的容器都是 root 的權限!對於某些人來說,實在不太喜歡。 podman 可以在一般帳號身份來管理容器!而且也能夠管理由 docker 所建立的映像檔與容器,相當值得了解一番。 不過,由於身份並非是 root,因此,如果容器需要使用到網路埠口時,就需要特別留意了!因為一般帳號不能啟動小於 1024 以下的埠口啊!。這也就是說,如果你要使用 podman 管理的容器去啟動小於 1024 以下的埠口時,你可能有兩個選擇:
      • 使用 root 的身份去操作 podman,就可以避過這個問題。不過,當然啦!有人就是不喜歡使用 root 啟動啊!
      • 用一般帳號啟動 podman,並且開啟 port 到大於 1024 的埠口,然後透過防火牆的 port mapping 功能, 指向非正規埠口即可。
  3. 安裝 container-tools 模組:
    1. client 系統的環境準備:
      由於我們的 Server 系統已經安裝了 docker 了,而 docker 與 podman 是互相衝突的 (兩個都在管理容器), 所以,這次我們同時啟動 server 與 client,其中 client 用來操作 podman。但是 server 必須要能夠提供網路才行。 因此,請在 client 上面,根據 server 的網路環境,設定好你的 client 網路!。
    2. 安裝容器管理軟體:
      雖然系統上面預設安裝有 podman 了,不過,我們可以透過 yum 的模組功能 (module) 來找到更多的應用! 如下所示:
      # yum module list
      Name                 Stream          Profiles Summary
      389-ds               1.4                      389 Directory Server (base)
      ant                  1.10 [d]        common [ Java build tool
      container-tools      rhel8 [d][e]    common [ Most recent (rolling) versions of podman..
                                           d]       ependencies such as container-selinux bu..
      container-tools      3.0             common [ Stable versions of podman 3.0, buildah 1..
                                           d]       -selinux built and tested together, and ..
      freeradius           3.0 [d]         server [ High-performance and highly configurable..
                                           d]
      ....
      
      想要安裝相關的 module 也挺簡單的,如上所示,我們想要安裝的是 container-tools,你只要安裝不要給予版本, yum 會主動的給最新版!所以,就這樣做即可:
      # yum module install container-tools
      
      系統就會主動的安裝好所需要的模組了!相當簡單方便!
  4. 實做練習:
    1. 在使用 yum 列出的模組中,找到最新版本的 python,並且安裝該 python 版本。
    2. 再次檢查 yum 的模組, python39 是否多了 [e] 的顯示?
    3. 可以使用『 yum module info python39 』列出更多的詳細資訊

簡易的操作 podman

  1. 第一次操作 podman 管理容器:
    1. 使用一般帳號開始查詢所需要的映像檔:
      如前所述, podman 可以讓用戶直接操作容器,所以請使用一般帳號 (誰都可以) 來操作才能發現 podman 的好處。 然後想想看前一章 docker 的處理方式,你會想到應該要先查詢有沒有映像檔在倉儲內才對吧!所以, 繼續讓我們來看看有沒有 rockylinux 呢?
      $ podman search rockylinux
      INDEX     NAME                             DESCRIPTION                  STARS OFFICIAL
      docker.io docker.io/rockylinux/rockylinux                               45
      docker.io docker.io/library/rockylinux     The official build of Rocky. 0     [OK]
      docker.io docker.io/106061/rockylinux_cicd For CI/CD RockyLinux         0
      ....
      
      看起來有兩個挺不錯的 rockylinux 可以使用~一個是喜好 (STARS) 比較多,另一個則是官方 (OFFICIAL) 推出的!我們直接拿第一個來安裝好了!要注意喔,即使是 podman,很多的 image 也是從 docker.io 這個公開的倉儲來的!
    2. 開始下載映像檔:
      找到正確的映像檔,再來就是下載他到本機上!
      $ podman pull docker.io/rockylinux/rockylinux
      Trying to pull docker.io/rockylinux/rockylinux:latest...
      Getting image source signatures
      Copying blob 72a2451028f1 done
      Copying config a1e37a3cce done
      Writing manifest to image destination
      Storing signatures
      a1e37a3cce8f954b7a802d41974c7cd8dbe8c529c7d9a253fba1d6cd679230f1
      
      $ podman images
      REPOSITORY                       TAG         IMAGE ID      CREATED      SIZE
      docker.io/rockylinux/rockylinux  latest      a1e37a3cce8f  4 weeks ago  211 MB
      
      很快的,將需要的映像檔抓下來,也能夠進行第一次的觀察!另外,從上面的結果,你也可以發現到映像檔的命名大致上是這樣的:
      • docker.io/rockylinux/rockylinux:latest
      • 倉儲名稱/建立者或單位名稱/實際映像檔名稱:版本(tag)
    3. 開始啟動 container:
      啟動容器也是使用 podman 啦!方法跟 docker 幾乎一模一樣!連參數設計都相同!假設我要設定一個名為 rocky1 的容器名稱, 使用的就是剛剛抓下來的映像檔,就這樣做即可:
      $ podman run -it --name rocky1 docker.io/rockylinux/rockylinux
      [root@71875f3ccaae /]# ls /etc/yum.repos.d/
      Rocky-AppStream.repo  Rocky-Debuginfo.repo  Rocky-Extras.repo
      Rocky-Media.repo      Rocky-Plus.repo       Rocky-RT.repo
      ...
      
      [root@71875f3ccaae /]# yum whatprovides '*bin/ps'
      procps-ng-3.3.15-6.el8.i686 : System and process monitoring utilities
      Repo        : baseos
      Matched from:
      Provide    : /bin/ps
      ....
      
      [root@71875f3ccaae /]# yum install procps-ng net-tools iproute
      
      [root@71875f3ccaae /]# ps aux
      USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
      root           1  0.0  0.1  19352  3584 pts/0    Ss   15:08   0:00 /bin/bash
      root          70  0.0  0.1  51864  3668 pts/0    R+   15:14   0:00 ps aux
      
      [root@71875f3ccaae /]# ip addr show
      1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
          inet6 ::1/128 scope host
             valid_lft forever preferred_lft forever
      2: tap0: <BROADCAST,UP,LOWER_UP&t; mtu 65520 qdisc fq_codel state UNKNOWN group default..
          link/ether f2:eb:43:03:21:61 brd ff:ff:ff:ff:ff:ff
          inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
             valid_lft forever preferred_lft forever
          inet6 fe80::f0eb:43ff:fe03:2161/64 scope link
             valid_lft forever preferred_lft forever
      
      [root@71875f3ccaae /]# [ctrl]-p [crtl]-q
      $   <==這樣就回到原本的 host 狀態,而不是 container 中!
      
    4. 觀察 container 與取回 container 控制:
      跟 docker 也是差不多,要觀察 podman 時,使用 podman ps 或者是加上 -a 的參數,都可以看到運作中或者是全部的 podman container 喔!
      $ podman ps
      CONTAINER ID  IMAGE                 COMMAND     CREATED       .. NAMES
      71875f3ccaae  docker.io/rockylin... /bin/bash   16 minutes ago.. rocky1
      
      目前僅有一個 container 在運作,我們也剛剛脫離 rocky1 啦!現在,讓我們繼續取得這個 container 之後, 直接關閉他吧。
      $ podman attach rocky1
      [root@71875f3ccaae /]# exit
      
      $ podman ps -a
      CONTAINER ID  IMAGE                 .. COMMAND   .. STATUS                    .. NAMES
      71875f3ccaae  docker.io/rockylinux/r.. /bin/bash .. Exited (0) 20 seconds ago .. rocky1
      
  2. 管理既有的 container 方式:
    1. 用 start 與 attach 重新啟動與連接之前建立的 container :
      以剛剛建立的 rocky1 為例,當我們離開 container (exit 指令下達後) 之後,事實上這個 container 還是存在的 若需要再次的啟動,那就使用底下的方式來處理:
      $ podman ps -a
      CONTAINER ID  IMAGE                 .. COMMAND   .. STATUS                    .. NAMES
      71875f3ccaae  docker.io/rockylinux/r.. /bin/bash .. Exited (0) 20 seconds ago .. rocky1
      
      $ podman start rocky1
      rocky1
      
      $ podman attach rocky1
      
      [root@71875f3ccaae /]# ps -l
      F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
      4 S     0       1       0  0  80   0 -  4810 -      pts/0    00:00:00 bash
      0 R     0      12       1  0  80   0 - 12435 -      pts/0    00:00:00 ps
      
      [root@71875f3ccaae /]# [crtl]-p [crtl]-q
      $ podman ps 
      CONTAINER ID  IMAGE                .. COMMAND   .. STATUS           .. NAMES
      71875f3ccaae  docker.io/rockylinux/.. /bin/bash .. Up 2 minutes ago .. rocky1
      
      這樣就啟動上次建立的 container 了!而且也可以跟 container 互動!取得操作方式!
    2. 用 exec 執行 container 內的指令:
      假設 container 執行的指令並不是 /bin/bash 時,例如啟動 apache 的 container ,啟動的是服務, 而不是 bash 啊!那麼你想要跟 container 互動,就得要使用 exec 來處理才行:
      $ podman exec [-it] 容器名稱 指令列
      $ podman exec rocky1 ps aux
      USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
      root           1  0.0  0.2  19240  3740 pts/0    Ss+  17:14   0:00 /bin/bash
      root          23  0.0  0.1  44676  3428 ?        Rs   17:31   0:00 ps aux
      
      $ podman exec -it rocky1 bash
      [root@71875f3ccaae /]# ps aux
      USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
      root           1  0.0  0.2  19240  3740 pts/0    Ss+  17:14   0:00 /bin/bash
      root          45  0.1  0.1  19240  3648 pts/1    Ss   17:33   0:00 bash
      root          57  0.0  0.2  51864  3800 pts/1    R+   17:33   0:00 ps aux
      
      [root@71875f3ccaae /]# exit
      $ podman ps
      CONTAINER ID  IMAGE                 .. COMMAND   .. STATUS            .. NAMES
      71875f3ccaae  docker.io/rockylinux/r.. /bin/bash .. Up 20 minutes ago .. rocky1
      
      你會發現,我們使用 ps 時,podman 會跑去 container 裡面執行指令,執行完畢就結束了!並不會停留在 container 內。 但如果是可以有互動的 bash 指令,那加上 -it 之後,你就會在 container 內停留操作 bash, 並且此時就有兩個 bash 在 container 內!因此,你離開這個 bash 時,並不會干擾到原本的 bash 程序! 所以,這個 container 還是會持續存在記憶體內!並不會關閉的!
    3. 用 logs 呼叫出 container 目前的終端內容:
      有個比較有趣的 podman 指令,那就是 logs !這個 logs 會將 container 的所有操作行為都列出來! 如果我們想要看看 rocky1 從啟動到目前為止的操作行為,可以這樣做:
      $ podman logs rocky1
      ....
      [root@71875f3ccaae /]# exit
      exit
      [root@71875f3ccaae /]# ps -l
      F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
      4 S     0       1       0  0  80   0 -  4810 -      pts/0    00:00:00 bash
      0 R     0      12       1  0  80   0 - 12435 -      pts/0    00:00:00 ps
      $ 
      
      內容相當龐大,自己瞧瞧就好了!上面所有的資料都只是顯示而已,相當有趣喔!
    4. 用 cp 將 container 內的檔案複製出來:
      某些時刻,你可能需要將 container 內的資料複製到 host 上面,此時,可以透過 podman cp 來處理!
      $ podman cp rocky1:/etc/hosts .
      $ ll hosts
      -rw-r--r--. 1 student student 288 Dec 19 01:14 hosts
      
      $ podman cp rocky1:/var/lib .
      $ ll -d lib
      
    5. 用 stop 關閉正在運作中的 container:
      這個就簡單了!關閉 container 而已!
      $ podman ps
      ... (你會看到 rocky1 正在活動中)
      
      $ podman stop rocky1
      rocky1
      
      $ podman ps
      
      這樣關閉了 rocky1 囉!
  3. 實做練習:
    1. 找一下名為 httpd 的 image,最好是由 redhat.com 這個倉儲內的 ubi8 這個單位所推出的較佳。
    2. 將上述的 image 抓到本地端來 (偶而會顯示 TLS timeout,重新執行幾次即可)
    3. 利用此 image 建立名為 myhttpd 的容器名稱,請使用 podman 搭配 create 指令操作測試。 不要進入互動界面 (不要 -it 之意。)
    4. 觀察上述容器是否活動中?若沒有在活動中,請啟動該容器,但是同樣不要進入該容器
    5. 能否使用 attach 連接到 myhttpd 呢?使用 podman 似乎無法離開該 container,或許只能關閉了。
    6. 承上,如果關閉了,請重新啟動。
    7. 嘗試使用 exec 的方式,再次執行一個 bash 在 myhttpd 容器內。
    8. 在容器內以 ps aux 查詢啟動的程序有哪些?
    9. 在容器內查看一下 /etc/system-release 的結果為何?
    10. 在容器內離開 bash !
    11. 嘗試刪除 myhttpd 這個容器!
  4. 不保留 container 的方式,透過 --rm 功能:
    1. container 一般存在的角色:
      基本上,container 都是用到才啟動!然後,相關的資料通通放置在某個 host 目錄下,關閉 container 時, 大部分連同 container 本身都是可以刪除的!目的不在永遠存在啦!有需要才多開幾個容器來支援這樣。 所以如同上面練習最後一題,我們很可能在每次停止 container 之後,都要手動刪除該容器呢!
    2. 在關閉時直接刪除容器的方法: --rm: 要實做容器關閉就刪除該容器也很簡單,加上 --rm 在 podman run 的階段即可!例如,再啟動一個名為 rocky2 的容器, 使用 rockylinux 映像檔,然後離開 bash 時,觀察看看容器有沒有被刪除呢?
      $ podman run -it --rm --name rocky2 rockylinux/rockylinux
      [root@92eb4fed4bc1 /]# cat /etc/system-release
      Rocky Linux release 8.5 (Green Obsidian)
      
      [root@92eb4fed4bc1 /]# exit
      
      $ podman ps -a
      
      你會發現到,基本上,沒有 rocky2 容器的存在!你離開 bash,該容器就主動被刪除了!這就是 --rm 的功效! 要注意喔,很多容器的使用,就是加上這個動作的!

簡易的倉儲與映像檔查詢管理

  1. 預設的映像檔倉儲:
    1. 倉儲的位置:
      在 RHEL 衍生的 Linux distributions 上面,預設的 podman 會有底下的映像檔倉儲:
      • "registry.fedoraproject.org"
      • "registry.access.redhat.com"
      • "registry.centos.org"
      • "docker.io"
    2. 映像檔倉儲的設定檔:
      相關的設定檔其實都放置到 /etc/containers/registries* 裡面,我們先來看看主要的設定檔:
      $ grep -v '^#' /etc/containers/registries.conf
      unqualified-search-registries = ["registry.fedoraproject.org", "registry.access.redhat.com",
                                       "registry.centos.org", "docker.io"]
      short-name-mode = "permissive"
      
      從上面也看得出來,主要設定就是這幾個!其他的設定檔也都瞧瞧就好!預設值已經很好! 除非你有額外想要安裝的倉儲時,再來修改這些設定檔即可。
    3. 查看既有的 registry 設定以及相關設定值:
      事實上,使用 podman 就可以看到很多資訊了!
      $ podman info
      ....
      registries:
        search:
        - registry.fedoraproject.org
        - registry.access.redhat.com
        - registry.centos.org
        - docker.io
      store:
        configFile: /home/student/.config/containers/storage.conf
        containerStore:
          number: 1
      .....
        volumePath: /home/student/.local/share/containers/storage/volumes
      .....
      
      這樣能夠看到預設的倉儲位置,以及未來儲存資料時,預設的資料放置目錄哩!
  2. 搜尋映像檔的其他特殊選項:
    1. 選擇特定的倉儲來搜尋:
      通常我們都直接使用 podman search 直接找映像檔關鍵字。如果你有特別的需求,需要在特定的軟體倉儲搜尋時, 舉例來說,我們知道 rockylinux 應該不會出現在 redhat 相關的倉儲...那就可以這樣做:
      $ podman help search
      $ podman search docker.io/rockylinux
      INDEX       NAME                              DESCRIPTION                        
      docker.io   docker.io/rockylinux/rockylinux                                     
      docker.io   docker.io/library/rockylinux      The official build of Rocky Linux
      docker.io   docker.io/106061/rockylinux_cicd  For CI/CD RockyLinux            
      
      意思是倉儲名稱之後加上關鍵字,就可以取得相關的映像檔名稱囉!不是太困難~那如果你只想要某些特定的關鍵字, 例如僅列出 5 筆資料,或者是只列出官方 (official) 的映像檔,也可以這樣:
      $ podman search docker.io/rockylinux --limit 5
      $ podman search docker.io/rockylinux --filter is-official=true
      
      過濾 (filter) 可以家的選項有:
      • --filter stars=數量
      • --filter is-automated=[true|false]
      • --filter is-official=[true|false]
    2. 查看下載的容器資訊:
      使用 podman 裡頭的 image 管理中,可以透過 inspect 來查看一些映像檔的資訊喔:
      $ podman image inspect docker.io/rockylinux/rockylinux
      [
          {
      ....
              "Created": "2021-11-15T21:47:03.053188004Z",
              "Config": {
                  "Env": [
                      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
                  ],
                  "Cmd": [
                      "/bin/bash"
                  ],
                  "WorkingDir": "/",
              "Created": "2021-11-15T21:47:03.053188004Z",
      ....
      
  3. 實做練習:
    1. 找出 registry.access.redhat.com 裡面關於 python 的容器有哪些?
    2. 請下載最新的 python,應該要下載的是 ubi8 的組織所打包的版本。
    3. 列出 python 這個映像檔的詳細資訊,並且特別查看比較重要的 Config 部份資料, 包括 Ports 對應、環境變數、實際啟動指令 (Cmd) 等資訊
    4. 刪除此映像檔。

稍進階 container 管理:port mapping, volume...

  1. 設定好 port mapping 功能:
    1. 觀察一個 httpd 的服務方式:
      我們前面的練習當中,有下載過 httpd 的映像檔,觀察一下映像檔的內容吧!
      $ podman inspect registry.access.redhat.com/ubi8/httpd-24
      .....
              "Config": {
                  "User": "1001",
                  "ExposedPorts": {
                      "8080/tcp": {},
                      "8443/tcp": {}
      ....
                  "Entrypoint": [
                      "container-entrypoint"
                  ],
                  "Cmd": [
                      "/usr/bin/run-httpd"
                  ],
      .....
      
      看起來這個映像檔似乎會啟用一個 8080 以及 8443 的埠口給容器使用喔! 同時啟動 container 時,會主動喚醒 /usr/bin/run-httpd 這個腳本程式就是了。
    2. 使用 daemon 的方式啟動容器:
      我們前面都是使用 podman run -it... 來啟動一個容器,事實上,如果以上面 httpd-24 的映像檔為例, 他就主要提供一個 web service,而不是要跟你直接互動啊!所以,你當然可以直接讓該容器啟動在背景上!
      $ podman run -d --name myhttpd registry.access.redhat.com/ubi8/httpd-24
      $ podman ps
      CONTAINER ID  IMAGE                  .. COMMAND              .. STATUS      PORTS  NAMES
      1198d20a0eaa  registry.access.redhat... /usr/bin/run-http... .. Up 11 secon        myhttpd
      
      這樣就執行了一個不需要互動,且持續在執行當中的容器了!
    3. 讓本機可以連上虛擬機器來取得網頁服務 -p 功能!
      現在想一想,上面的容器就是 httpd 提供的一個服務,所以,你得要使用瀏覽氣去測試他對吧。 好!請問 IP 在哪裡?請問 port 在哪裡?哈哈!沒錯!其實你根本無法在 container 之外的環境連接到該網頁服務! 所以,這樣根本無法提供網際網路實際處理的。沒關係,我們可以跟 docker 一樣,透過『 -p 本機埠口:容器埠口』 來設定好連線的!
      $ podman stop myhttpd
      $ podman rm myhttpd
      $ podman run -d --name myhttpd -p 8080:8080 registry.access.redhat.com/ubi8/httpd-24
      $ podman port -l
      8080/tcp -> 0.0.0.0:8080
      $ curl http://localhost:8080
      $ podman port -a
      a2668e3a2eac    8080/tcp -> 0.0.0.0:8080
      
    4. 修改 myhttpd 的首頁內容:
      雖然上面已經可以順利的從 host 去取得網頁資料,不過,我們還是嘗試來修改一下首頁好了! 免得誤會是跑去哪裡了~
      $ podman exec -it myhttpd bash
      bash-4.4$ echo 'This is from podman container' > /var/www/html/index.html
      bash-4.4$ exit
      
      $ curl http://localhost:8080
      This is from podman container
      
      很簡單的取得了我們需要的網頁資料囉!
  2. 設定本機 host 的目錄,讓 container 去掛載:
    1. 讓 container 掛載本機目錄的需求:
      以上面的案例來分析,如果我們需要修改網頁還得要連線到該容器內修改!總覺得困惑。 沒關係,我們可以透過本機的目錄分享給 container 使用,這樣,在本機目錄內修改完畢, container 就可以立刻生效了!這個需求是很重要的。有時候我們在加上 --rm 的方式啟動容器, 所以容器關閉就刪除!問題是,我們希望將某些資料保留下來時,就可以透過這個功能了!
    2. 實做目錄掛載
      假設要將本機的 ~/server/www 掛載到網頁根目錄,亦即是 /var/www/html 底下時,除了 podman 本身的 -v 設定之外, 你應該要先解決 SELinux 的 container 類型問題!通常使用的 SELinux type 為『 container_file_t 』。所以,你可以嘗試這樣做:
      $ mkdir server; mkdir server/www
      $ chcon -Rv -t container_file_t ~/server
      
      $ podman stop myhttpd
      $ podman rm myhttpd
      
      $ podman run -d --name myhttpd -p 8080:8080 -v ~/server/www:/var/www/html \
        registry.access.redhat.com/ubi8/httpd-24
      
      $ echo 'This is from host webpage' > ~/server/www/index.html
      $ curl http://localhost:8080/index.html
      This is from host webpage
      
      $ podman exec -it myhttpd cat /var/www/html/index.html
      This is from host webpage
      
      這功能倒是挺重要的!當有任何需要保留的資料,都可以使用這個方法來處理。只是要特別注意 SELinux 的類型就是了! 通常使用者都會卡在這個地方...
    3. 查看某容器的詳細資料:
      容器的詳細資料可以透過『 podman inspect containername 』來處理。如果只想要看 volume 的資訊,可以這樣做:
      $ podman inspect myhttpd | grep -A 10 Mounts
              "Mounts": [
                  {
                      "Type": "bind",
                      "Source": "/home/student/server/www",
                      "Destination": "/var/www/html",
                      "Driver": "",
                      "Mode": "",
                      "Options": [
                          "rbind"
                      ],
                      "RW": true,
      
  3. 網路查詢功能:
    1. 預設的 podman 容器的網路處理方式:

      podman 的網路是透過一個 slirp4netns 機制處理的,在預設的情境下 (v0.4.0版本之後),podman 容器根據這個機制的設定, 會主動帶起一個名為 tap0 的裝置,固定的 IP 都會是 10.0.2.100,gatway 則是 10.0.2.2 而 DNS 則是 10.0.2.3。 當然可以透過 slirp4netns 指令去修改,詳細的資訊可以『 man slirp4netns 』去查閱一下。

      不過,由於 slirp4netns 在 podman 預設的情況下 (沒有額外帶入參數時),所有的 container 都會是相同的 IP, 所以,雖然容器很容易喚醒網路,且網路效能真的還不賴~但是,容器與容器彼此之間,幾乎是沒有辦法互相溝通的! 這就是個大的困擾!

      # 建立一個名為 rocky3 的容器,使用 rockylinux 映像檔,且關閉後刪除
      $ podman run -it --rm --name rocky3 docker.io/rockylinux/rockylinux
      [root@e9c2b63ba122 /]# yum -y install net-tools
      [root@e9c2b63ba122 /]# ifconfig
      ....
      tap0: flags=67<UP,BROADCAST,RUNNING>  mtu 65520
              inet 10.0.2.100  netmask 255.255.255.0  broadcast 10.0.2.255
      ....
      
      [root@e9c2b63ba122 /]# 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
      
      [root@e9c2b63ba122 /]# [ctrl]-p [ctrl]-q
      
      # 查詢 rocky3 的網路模式:
      $ podman inspect rocky3 | less
      ....
              "NetworkSettings": {
                  "EndpointID": "",
                  "Gateway": "",
                  "IPAddress": "",
                  "IPPrefixLen": 0,
                  "IPv6Gateway": "",
                  "GlobalIPv6Address": "",
                  "GlobalIPv6PrefixLen": 0,
                  "MacAddress": "",
                  "Bridge": "",
                  "SandboxID": "",
                  "HairpinMode": false,
                  "LinkLocalIPv6Address": "",
                  "LinkLocalIPv6PrefixLen": 0,
                  "Ports": {},
                  "SandboxKey": "/run/user/1000/netns/cni-7fbda5e1-409c-6a0a-e03a-7fb566b08685"
              },
      ....
                  "NetworkMode": "slirp4netns",
      ....
      
      不管你開幾個 container,每一個 container 的網路參數卻是一模一樣的!這是透過上述的 slirp4netns 協助的功能! 此外,其實網路堆疊是放置在 /run/user/使用者ID/netns/ 裡面啦!也因為如此,所以,所有的 container 其實是被鎖在自己的網路環境下, 彼此是無法溝通的!我們也只能透過上一小節談到的 port mapping 功能,從 host 連線到 container 而已。
    2. podman 的私有網路:
      前幾章的 docker 網路環境中,所有的 docker 容器都是透過 docker0 的公開橋接界面來連結,所以在 host 上面, 可以簡單的透過 ip addr show 等,就可以看到各個界面了。但是 podman 並不相同,由於可以讓一般用戶自行設定網路, 所以使用 ip addr show 時,基本上,看不到網路的相關狀態的。
      $ ip addr show
      1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
      ...
      2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default..
      ...
      # 基本上,你看不到 container 所連接的網路情境。
      
      如果要觀察相關的設定,例如上面提到的類似 docker0 的界面,只能夠過『 podman network 』相關功能! 例如找到 podman 網路設定,並且觀察的方式如下:
      $ podman network ls
      NETWORK ID    NAME        VERSION     PLUGINS
      2f259bab93aa  podman      0.4.0       bridge,portmap,firewall,tuning
      
      $ podman network inspect podman
      ....
             "ranges": [
                 [
                     {
                         "gateway": "10.88.0.1",
                         "subnet": "10.88.0.0/16"
                     }
      ....
      
      如上所示,基本上,我們有一個內建的網路名稱為 podman 喔!該 podman 的 router 為 10.88.0.1,並且提供一個 class B 的網段給大家使用。
    3. 開始使用 podman 提供的網段來分配 container 的網路:
      事實上,啟動 container 時,可以呼叫上述的網路界面來提供相關的網路參數給 container 喔!就等於是讓 podman 成為一個大家共有的橋接器 (bridge) 就是了!整個方法很簡單~使用『 --network podman 』處理即可!
      # 分別啟動 rocky4, rocky5 兩個 container,使用網路界面 podman,
      # 啟動後直接離開 [crtl]-p [ctrl]-q 即可
      $ podman run -it --rm --name rocky4 --network podman docker.io/rockylinux/rockylinux
      $ podman run -it --rm --name rocky4 --network podman docker.io/rockylinux/rockylinux
      $ podman inspect rocky4 | grep IPAddress
                  "IPAddress": "",
                          "IPAddress": "10.88.0.2",
      $ podman inspect rocky5 | grep IPAddress
                  "IPAddress": "",
                          "IPAddress": "10.88.0.3",
      
      這樣就可以知道目前 rocky4, rocky5 的網路設定值!雖然從本機還是無法連線過去~不過,container 應該是可以互連的!
      $ podman attach rocky4
      [root@b4f608786dc1 /]# ping -c 2 10.88.0.2
      [root@b4f608786dc1 /]# ping -c 2 10.88.0.3
      [root@b4f608786dc1 /]# exit
      
      $ podman rm rocky5 -f
      
      做完測試後,就關閉兩個容器吧!

透過 pod 管理容器類群

  1. pod 的功能-將整個網路統一分享:

    pod 的概念是由 K8S 這套軟體引進的,基本概念就是,許多的容器共用一組網路參數,簡單的說,你可以想成, 一部主機上面有多個共同的服務在跑的感覺~而且全部都綁在同一個網路參數上面!因次,容器彼此之間, 可以透過共有的 port number 來進行交流~例如某個 rockylinux 的 bash 可以透過 mysql 指令連線到資料庫的 port 3306 資料庫軟體,而無須增加任何設定!

    這樣的設計方式,有點像是將某個沙箱 (sandbox) 設定好,並且給予適當的網路參數與權限後,將一堆程序 (容器) 丟進去! 所以,全部的容器通通可以混在一起玩!簡直就跟本機 (http://127.0.0.1) 一樣!大概就是這樣的想法!

  2. 將整個網路功能統一分享
    1. 一個範例:
      現在,假設我們要建立一個放置在容器當中的 Web 網頁伺服器系統,他需要一個 httpd 服務 (port 8080),一個資料庫系統 (mysql, port 3306), 一個具有 bash 的管理容器 (使用 rockylinux 好了)。當然啦,這是個簡單到爆炸的範本,所以沒有其他額外的服務 (例如還得要 FTP 或 SSH 等, 才能提供用戶傳輸資料上來的特性等,先放過~)。
    2. 先建立一個具有網路結構的 pod 提供:
      再次強調,這個 pod 需要提供網路,需要提供埠口對應,假設 pod 名稱為 web_service,那麼你可以這樣做:
      $ podman pod create --name web_service --network podman -p 8080:8080/tcp \
        -p 2222:22/tcp -p 3306:3306/tcp
      $ podman pod ps
      POD ID        NAME         STATUS      CREATED        INFRA ID      # OF CONTAINERS
      5f6950eeddcb  web_service  Created     5 seconds ago  6c48399ddbfa  1
      
      $ podman pod inspect web_service
      {
           "Id": "5f6950eeddcbd072ddb844c2164a894737db25e5cae6c8744c51ec2814f9bd1d",
           "Name": "web_service",
           "Created": "2021-12-22T00:18:28.868221389+08:00",
           "CreateCommand": [
                "podman",
                "pod",
                "create",
                "--name",
                "web_service",
                "--network",
                "podman",
                "-p",
                "8080:8080/tcp",
                "-p",
                "2222:22/tcp",
                "-p",
                "3306:3306/tcp"
      ....
      
      $ podman ps -a
      CONTAINER ID  ... PORTS                                        .. NAMES
      6c48399ddbfa  ... 0.0.0.0:2222->22/tcp, 0.0.0.0:3306->3306/tcp,.. 5f6950eeddcb-infra
      
      
      如上,你會發現有個 pod 名稱為 web_service,其中還有個很特別的『 registry.access.redhat.com/ubi8/pause 』映像檔存在! 這個是執行 pod 時,系統主動幫你暫時加上來的一個很特別的容器喔!
    3. 加入容器們:
      現在,讓我們加入一個 httpd,一個 mysql,一個 rockylinux。先加入網頁伺服器~方式如下:
      $ podman run -d --rm --name web_apache --pod web_service \
        registry.access.redhat.com/ubi8/httpd-24
      $ podman inspect web_apache | grep IPAddr
                  "IPAddress": "",
                          "IPAddress": "10.88.0.2",
      
      $ podman ps
      CONTAINER ID  IMAGE                                            COMMAND          \
            CREATED             STATUS                 PORTS                          \
                                             NAMES
      6c48399ddbfa  registry.access.redhat.com/ubi8/pause:latest                      \
            11 minutes ago      Up About a minute ago  0.0.0.0:2222->22/tcp, 0.0.0.0:3\
       306->3306/tcp, 0.0.0.0:8080->8080/tcp  5f6950eeddcb-infra
      5db082790cc4  registry.access.redhat.com/ubi8/httpd-24:latest  /usr/bin/run-http\
       ...  About a minute ago  Up About a minute ago  0.0.0.0:2222->22/tcp, 0.0.0.0:3\
       306->3306/tcp, 0.0.0.0:8080->8080/tcp  web_apache
      
      幾個重點注意一下:(1) 預設取得的 IP 在 pod 裡面都是相同的! (2) 所有的埠口對應在 pod 裡面都是相同的!所以最後執行 podman ps 時,有 3 行的輸出當中,兩個 container 的埠口對應完全相同喔!然後,再來處理一下 mysql 喔!
      $ podman search mysql
      INDEX       NAME                       DESCRIPTION                          STARS
      .....
      docker.io   docker.io/library/mariadb  MariaDB Server is a high performing  4512
      .....
      
      $ podman pull docker.io/library/mariadb
      
      $ podman run -d --rm --name web_mariadb --pod web_service \
        -e MARIADB_ROOT_PASSWORD=mypassword docker.io/library/mariadb
      
      $ podman ps
      ONTAINER ID   IMAGE                             PORTS                 .. NAMES
      554720b853fe  docker.io/library/mariadb:latest  0.0.0.0:2222->22/tcp, ..  web_mariadb
      
      這樣就多一個 mysql 囉!因為我們的 pod 有指定 3306 會指向該資料庫,因此,你可以這樣測試看看:
      $ mysql -h 127.0.0.1 -P 3306 -u root  -p   # 不能用 localhost 喔!
      Enter password:
      Welcome to the MariaDB monitor.  Commands end with ; or \g.
      Your MariaDB connection id is 3
      Server version: 10.6.5-MariaDB-1:10.6.5+maria~focal mariadb.org binary distribution
      
      Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
      
      Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
      
      MariaDB [(none)]> quit
      
      最後,加上管理用的 rockylinux 系統如下:
      $ podman run --rm -it --name web_opt --pod web_service \
        docker.io/rockylinux/rockylinux
      
      [root@web_service /]# 
      
      非常有趣的是,你會發現到,主機名稱變成 pod 的名稱喔!而且我們已經進入了 rockylinux 啦! 讓我們在這個 container 裡面工作一下:
      [root@web_service /]# yum -y install net-tools mysql
      [root@web_service /]# ifconfig
      eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
              inet 10.88.0.2  netmask 255.255.0.0  broadcast 10.88.255.255
              inet6 fe80::8e7:f6ff:fe7f:6b5a  prefixlen 64  scopeid 0x20
              ether 0a:e7:f6:7f:6b:5a  txqueuelen 0  (Ethernet)
              RX packets 19057  bytes 26778990 (25.5 MiB)
              RX errors 0  dropped 0  overruns 0  frame 0
              TX packets 10111  bytes 556728 (543.6 KiB)
              TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
      ....
      # 你會發現到,即使是第三個 container 了!IP 還是跟最初的 httpd server 相同!
      
      [root@web_service /]# netstat -tlunp
      Active Internet connections (only servers)
      Proto Recv-Q Send-Q Local Address   Foreign Address  State       PID/Program name
      tcp        0      0 0.0.0.0:3306    0.0.0.0:*        LISTEN      -
      tcp        0      0 0.0.0.0:8080    0.0.0.0:*        LISTEN      -
      tcp        0      0 0.0.0.0:8443    0.0.0.0:*        LISTEN      -
      tcp6       0      0 :::3306         :::*             LISTEN      -
      
      這就更有趣了!竟然還有看到其他的服務啟動的埠口哩!那麼,在這個地方,能不能用 rockylinux 登入 mysql 呢? 測試就對了!
      [root@web_service /]# mysql -h 127.0.0.1 -u root -p
      Enter password: <==輸入密碼
      Welcome to the MySQL monitor.  Commands end with ; or \g.
      Your MySQL connection id is 4
      Server version: 5.5.5-10.6.5-MariaDB-1:10.6.5+maria~focal mariadb.org binary distribution
      
      Copyright (c) 2000, 2021, Oracle and/or its affiliates.
      
      Oracle is a registered trademark of Oracle Corporation and/or its
      affiliates. Other names may be trademarks of their respective
      owners.
      
      Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
      
      mysql> exit
      Bye
      
      很快的,確定可以登入 pod 內的其他容器!且注意看 IP 喔!就感覺是自己的 IP!這就是 pod 的功能!

一般帳號的 systemd 管理容器的開機啟動行為

  1. podman 管理的容器何時啟動:
    1. 因為 podman 支援一般帳號來管理容器 (rootless 的環境),過去我們的認知裡面,一般帳號似乎沒有辦法在開機的時候, 就自動喚醒某些一般帳號自己的服務功能!所以就很麻煩!
    2. 現在,新的 systemd 管理機制,可以讓用戶透過 systemctl 搭配 --user 的方式,來處理自己的服務!並且跟 root 的角色一樣, 可以透過 enable 來讓系統主動喚醒這些一般帳號的服務喔!
    3. 只是,一般來說,這種一般用戶主動喚醒的服務,只有『使用者第一次登入時』才會喚醒!而不像 root, 在系統啟動之後就主動喚醒。這個是與系統最大的不同!但是也能夠改變啦~透過 loginctl 來修改~
  2. 讓一般用戶可以具有 systemctl enable 的權限:
    1. 啟動 enable-linger: 如前所述,如果要讓一般用戶設定 systemd 為 enable,且可以讓這個服務在開機時啟動,而不是登入後才啟動, 那用戶自己得要啟動名為 loginctl enable-linger 的功能才行!
      $ loginctl --help
      $ loginctl show-user student
      UID=1000
      GID=1000
      Name=student
      Timestamp=Fri 2021-12-17 06:37:20 CST
      TimestampMonotonic=1840394036
      RuntimePath=/run/user/1000
      Service=user@1000.service
      Slice=user-1000.slice
      Display=2
      State=active
      Sessions=2
      IdleHint=no
      IdleSinceHint=1639775337931783
      IdleSinceHintMonotonic=82937616851
      Linger=no
      
      $ loginctl enable-linger student
      $ loginctl show-user student
      .....
      Linger=yes
      
  3. 開始使用 systemctl --user 管理用戶的服務:
    1. 了解用戶的 systemd 設定檔位置:
      基本上,用戶的 systemd 設定檔位置放置於:
      • ~/.config/systemd/user/
      不過,這個目錄預設是不存在的!我們未來可以使用 podman 產生一些設定檔~目前先知道存放的位置在此即可!
    2. 使用 systemctl 管理,需要登入的模式:
      比較特別的是,你不能使用 su 或 sudo 來處理用戶的 systemd 服務,得要完整的登入系統! 例如透過本機的 tty 登入,或者是透過本機的圖形界面,或者是直接以 ssh 登入,否則不能操作 systemctl 喔!
      # 使用 su - student 的情境下:
      $ systemctl --user daemon-reload
      Failed to connect to bus: No such file or directory
      
      # 正常使用 tty 或者是 ssh student@localhost 的情境下,不會有錯誤訊息發生的!
      $ systemctl --user daemon-reload
      
    3. 使用 podman 的 generate 功能產生 systemd 範本檔:
      你當然可以從頭撰寫一隻 systemd 的設定檔,不過總是覺得有點麻煩~所以,我們可以透過 podman 的 generate 指令來達成一個簡易的設定檔,然後再根據自己的需求來修改該檔案即可。 不過需要注意,設定檔一定要放置到 ~/.config/systemd/user 目錄下喔!
      $ mkdir -p ~/.config/systemd/user
      $ cd ~/.config/systemd/user
      
      # 先使用 httpd 映像檔建立名為 myweb 的容器,且掛載 ~/server/www 到 /var/www/html
      $ podman run -d --rm --name myweb -v /home/student/server/www:/var/www/html \
        registry.access.redhat.com/ubi8/httpd-24
      
      # 以上面的 myweb 為範本,來建立 systemd 的設定檔:
      $ podman generate systemd --name myweb --new --files
      /home/student/.config/systemd/user/container-myweb.service
      --name container: 以 container 這個容器名稱為範本 (容器要先存在)
      --new           : 類似 --rm 的功能,關閉容器就刪除
      --files         : 在當前目錄建立設定檔
      
      $ cat container-myweb.service
      [Unit]
      Description=Podman container-myweb.service
      Documentation=man:podman-generate-systemd(1)
      Wants=network-online.target
      After=network-online.target
      RequiresMountsFor=%t/containers
      
      [Service]
      Environment=PODMAN_SYSTEMD_UNIT=%n
      Restart=on-failure
      TimeoutStopSec=70
      ExecStartPre=/bin/rm -f %t/%n.ctr-id
      ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --sdnotify=conmon \
         --cgroups=no-conmon --rm --replace -d --name myweb -v \
        /home/student/server/www:/var/www/html registry.access.redhat.com/ubi8/httpd-24
      ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
      ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
      Type=notify
      NotifyAccess=all
      
      [Install]
      WantedBy=multi-user.target default.target
      
      詳細的 systemd 設定檔請自行參考 systemd 的說明文件,上述設定當中,最重要的大概就是 ExecStart 以及 ExecStop 兩個啟動與關閉的腳本了!仔細檢查看看有沒有錯誤!有時候你可能需要做點修改就是了。
    4. 開始之前:一定要關閉原有的參考容器!
      systemd 比較特別的地方,在於 systemd 只會管理『自己啟動』的服務,所以你自己手動啟動的程序, systemd 是沒有辦法管理的。也因為這樣,剛剛我們自己建立的樣本容器: myweb 必須要先關閉才行啊!
      $ podman stop myweb
      $ podman ps -a
      # 這裡要確認不存在 myweb 的容器名稱才行喔!
      
    5. 啟動、開機啟動 container-myweb 服務:
      因為剛剛那個設定檔才建立而已,我們得要告知 systemctl 有多一個設定擋了! 因此得要先 daemon-reload 之後,才有辦法進行啟動與開機啟動喔!
      $ systemctl --user daemon-reload
      $ systemctl --user start container-myweb.service
      $ systemctl --user enable container-myweb.service
      $ podman ps
      # 這裡應該就可以看到 myweb 這個 container 的存在!
      
    6. 修改設定檔以及重新處理的方法:
      設定完畢之後才想到,我們沒有辦法可以測試這個 myweb 有沒有順利的載入各項資料! 因為沒有設計 port mapping 啊!該如何是好?沒關係,我們可以手動修改設定檔, 假設目前我們使用 8085 連接到 myweb:8080 時,可以這樣做:
      $ vim ~/.config/systemd/user/container-myweb.service
      .....
      ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --sdnotify=conmon \
         --cgroups=no-conmon --rm -p 8085:8080 --replace -d --name myweb -v \
         /home/student/server/www:/var/www/html registry.access.redhat.com/ubi8/httpd-24
      .....
      $ systemctl --user daemon-reload
      $ systemctl --user restart container-myweb.service
      $ podman ps
      # 這裡你應該就可以看到 8085 埠口會指向 8080 埠口了!
      
      $ echo 'check volume and url' > ~/server/www/myweb.html
      $ curl http://localhost:8085/myweb.html
      check volume and url
      
  4. 實做練習:
    1. 目的:想要讓 student 這個用戶,可以透過 systemd 的方式來管理自己的 container,且管理的角度為 pod 類群。為了避免干擾,請將目前系統上面的所有的 container 全部刪除。不過要注意,由 systemd 管理的, 請使用 systemd 的方式關閉。由 podman 手動處理的,請手動關閉與刪除。
    2. 依據上述要求,使用 student 身份,建立名為 wwws 的 pod 類群,且分別對應 8080 與 3306 兩個埠口, 同時使用 podman 的網路 (--network podman)
    3. 透過 registry.access.redhat.com/ubi8/httpd-24 這個映像檔,建立名為 www_httpd 的容器,且: (1) 加入 wwws 當中,(2)執行在背景環境,(3)不要停止後刪除 (取消 --rm 的意思)
    4. 透過 docker.io/library/mariadb 這個映像檔,建立名為 www_database 的容器,且: (1) 加入 wwws 當中,(2)執行在背景環境,(3)不要停止後刪除,(4)傳遞 MARIADB_ROOT_PASSWORD 變數內容,密碼請自取。
    5. 進入 ~/.config/systemd/user 目錄後,以 wwws 為名,建立 systemd 的設定檔。 你應該會發現共有 3 個檔案分別被產生出來!非常重要喔!
    6. 透過 podman 的方式來『 stop 』容器與 pod。要注意,容器不能刪除喔!否則 pod 會啟動不了! 重要!重要!
    7. 使用 systemctl --user 來管理,先 daemon-reload 之後,才可以啟動 pod-wwws 喔!
    8. 最終請觀察 podman ps 看看有沒有順利啟動所有的容器!

參考資料