Podman 使用指南

原文連接:Podman 使用指南html

Podman 原來是 CRI-O 項目的一部分,後來被分離成一個單獨的項目叫 libpod。Podman 的使用體驗和 Docker 相似,不一樣的是 Podman 沒有 daemon。之前使用 Docker CLI 的時候,Docker CLI 會經過 gRPC API 去跟 Docker Engine 說「我要啓動一個容器」,而後 Docker Engine 纔會經過 OCI Container runtime(默認是 runc)來啓動一個容器。這就意味着容器的進程不多是 Docker CLI 的子進程,而是 Docker Engine 的子進程。前端

Podman 比較簡單粗暴,它不使用 Daemon,而是直接經過 OCI runtime(默認也是 runc)來啓動容器,因此容器的進程是 podman 的子進程。這比較像 Linux 的 fork/exec 模型,而 Docker 採用的是 C/S(客戶端/服務器)模型。與 C/S 模型相比,fork/exec 模型有不少優點,好比:linux

  • 系統管理員能夠知道某個容器進程究竟是誰啓動的。nginx

  • 若是利用 cgroup 對 podman 作一些限制,那麼全部建立的容器都會被限制。git

  • SD_NOTIFY : 若是將 podman 命令放入 systemd 單元文件中,容器進程能夠經過 podman 返回通知,代表服務已準備好接收任務。github

  • socket 激活 : 能夠將鏈接的 socket 從 systemd 傳遞到 podman,並傳遞到容器進程以便使用它們。docker

廢話很少說,下面咱們直接進入實戰環節,本文將手把手教你如何用 podman 來部署靜態博客,並經過 Sidecar 模式將博客所在的容器加入到 Envoy mesh 之中。數據庫

1. 方案架構

個人部署方案涉及到兩層 Envoy:vim

  • 首先會有一個前端代理單獨跑一個容器。前端代理的工做是給訪問者提供一個入口,未來自外部的訪問請求轉發到具體的後端服務。後端

  • 其次,博客靜態頁面由 nginx 提供,同時以 Sidecar 模式運行一個 Envoy 容器,它與 nginx 共享 network nemspace

  • 全部的 Envoy 造成一個 mesh,而後在他們之間共享路由信息。

我以前寫過一篇用 Docker 部署 hugo 靜態博客並配置 HTTPS 證書的文章,本文采用的是相同的方案,只是將 docker 換成了 podman,具體參考爲 Envoy 開啓 TLS 驗證明戰

2. 部署 hugo 和 sidecar proxy

個人博客是經過 hugo 生成的靜態頁面,能夠將其放到 nginx 中,其餘靜態網站工具相似(好比 hexo 等),均可以這麼作。如今我要作的是讓 nginx 容器和 envoy 容器共享同一個 network namespace,同時還要讓前端代理可以經過域名來進行服務發現。之前用 docker 很簡單,直接用 docker-compose 就搞定了,podman 就比較麻煩了,它又不能用 docker-compose,服務發現看來是搞不定了。

好不容易在 Github 上發現了一個項目叫 podman-compose,覺得有救了,試用了一下發現仍是不行,podman-compose 建立容器時會將字段 network_mode: "service:hugo" 轉化爲 podman CLI 的參數 --network service:hugo(真腦殘),致使容器建立失敗,報錯信息爲 CNI network "service:hugo" not found。將該字段值改成 network_mode: "container:hugo_hugo_1" 能夠啓動成功,然而又引來了另外一個問題:podman-compose 的作法是爲每個 service 建立一個 pod(pod 的名字爲 docker-compose.yml 所在目錄名稱),而後往這個 pod 中添加容器。我總不能將前端代理和後端服務塞進同一個 pod 中吧?只能分別爲前端代理和 hugo 建立兩個目錄,而後分別建立 docker-compose.yml。這個問題解決了,下個問題又來了,podman-compose 不支持經過 service name 進行服務發現,扒了一圈發現支持 links(其實就是加個參數 --add-host),然而 links 只在同一個 pod 下才生效,我都拆分紅兩個 pod 了,links 鞭長莫及啊,仍是沒什麼卵用。我能怎麼辦,如今惟一的辦法就是手擼命令行了。

上面我提到了一個新名詞叫 pod,這裏花 30 秒的時間給你們簡單介紹一下,若是你是 Kubernetes 的重度使用者,對這個詞應該不陌生,但這裏確實說的是 podman 的 pod,意思仍是同樣的,先建立一個 pause 容器,而後再建立業務容器,業務容器共享 pause 容器的各類 linux namespace,所以同一個 pod 中的容器之間能夠經過 localhost 輕鬆地相互通訊。不只如此,podman 還能夠將 pod 導出爲 Kubernetes 的聲明式資源定義,舉個栗子:

先建立一個 pod:

$ podman pod create --name hugo
複製代碼

查看 pod:

$ podman pod ls

POD ID         NAME   STATUS    CREATED         # OF CONTAINERS INFRA ID
88226423c4d2   hugo   Running   2 minutes ago   2                 7e030ef2e7ca
複製代碼

在這個 pod 中啓動一個 hugo 容器:

$ podman run -d --pod hugo nginx:alpine
複製代碼

查看容器:

$ podman ps

CONTAINER ID  IMAGE                           COMMAND               CREATED        STATUS            PORTS  NAMES
3c91cab1e99d  docker.io/library/nginx:alpine  nginx -g daemon o...  3 minutes ago  Up 3 minutes ago         reverent_kirch
複製代碼

查看全部容器,包括 pause 容器:

$ podman ps -a

CONTAINER ID  IMAGE                           COMMAND               CREATED        STATUS            PORTS  NAMES
3c91cab1e99d  docker.io/library/nginx:alpine  nginx -g daemon o...  4 minutes ago  Up 4 minutes ago         reverent_kirch
7e030ef2e7ca  k8s.gcr.io/pause:3.1                                  6 minutes ago  Up 6 minutes ago         88226423c4d2-infra
複製代碼

查看全部容器,包括 pause 容器,並顯示容器所屬的 pod id:

$ podman ps -ap

CONTAINER ID  IMAGE                           COMMAND               CREATED        STATUS            PORTS  NAMES               POD
3c91cab1e99d  docker.io/library/nginx:alpine  nginx -g daemon o...  4 minutes ago  Up 4 minutes ago         reverent_kirch      88226423c4d2
7e030ef2e7ca  k8s.gcr.io/pause:3.1                                  6 minutes ago  Up 6 minutes ago         88226423c4d2-infra  88226423c4d2
複製代碼

查看 pod 中進程的資源使用狀況:

$ podman pod top hugo

USER    PID   PPID   %CPU    ELAPSED           TTY   TIME   COMMAND
root    1     0      0.000   8m5.045493912s    ?     0s     nginx: master process nginx -g daemon off;
nginx   6     1      0.000   8m5.045600833s    ?     0s     nginx: worker process
nginx   7     1      0.000   8m5.045638877s    ?     0s     nginx: worker process
0       1     0      0.000   9m41.051039367s   ?     0s     /pause
複製代碼

將 pod 導出爲聲明式部署清單:

$ podman generate kube hugo > hugo.yaml
複製代碼

查看部署清單內容:

$ cat hugo.yaml

# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-1.0.2-dev
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: 2019-10-17T04:17:40Z
  labels:
    app: hugo
  name: hugo
spec:
  containers:
  - command:
    - nginx
    - -g
    - daemon off;
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: HOSTNAME
    - name: container
      value: podman
    - name: NGINX_VERSION
      value: 1.17.4
    - name: NJS_VERSION
      value: 0.3.5
    - name: PKG_RELEASE
      value: "1"
    image: docker.io/library/nginx:alpine
    name: reverentkirch
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities: {}
      privileged: false
      readOnlyRootFilesystem: false
    workingDir: /
status: {}
複製代碼

怎麼樣,是否是有種熟悉的味道?這是一個兼容 kubernetes 的 pod 定義,你能夠直接經過 kubectl apply -f hugo.yaml 將其部署在 Kubernetes 集羣中,也能夠直接經過 podman 部署,步驟大體是這樣的:

先刪除以前建立的 pod:

$ podman pod rm -f hugo
複製代碼

而後經過部署清單建立 pod:

$ podman play kube hugo.yaml
複製代碼

回到以前的問題,若是經過聲明式定義來建立 pod,仍是沒法解決服務發現的問題,除非換個支持靜態 IP 的 CNI 插件,而支持靜態 IP 的這些 CNI 插件又須要 etcd 做爲數據庫,我就這麼點資源,可不想再加個 etcd,仍是手擼命令行吧。

首先我要建立一個 hugo 容器,並指定容器的 IP:

$ podman run -d --name hugo \
  --ip=10.88.0.10 \
  -v /opt/hugo/public:/usr/share/nginx/html \
  -v /etc/localtime:/etc/localtime \
  nginx:alpine
複製代碼

再建立一個 envoy 容器,與 hugo 容器共享 network namespace:

$ podman run -d --name hugo-envoy \
  -v /opt/hugo/service-envoy.yaml:/etc/envoy/envoy.yaml \
  -v /etc/localtime:/etc/localtime \
  --net=container:hugo envoyproxy/envoy-alpine:latest
複製代碼

service-envoy.yaml 的內容以下:

static_resources:
 listeners:
 - address:
 socket_address:
 address: 0.0.0.0
 port_value: 8080
 filter_chains:
 - filters:
 - name: envoy.http_connection_manager
 config:
 codec_type: auto
 stat_prefix: ingress_http
 access_log:
 - name: envoy.file_access_log
 config:
 path: "/dev/stdout"
 route_config:
 name: local_route
 virtual_hosts:
 - name: service
 domains:
 - "*"
 routes:
 - match:
 prefix: "/"
 route:
 cluster: local_service
 http_filters:
 - name: envoy.router
 config: {}
 clusters:
 - name: local_service
 connect_timeout: 0.25s
 type: strict_dns
 lb_policy: round_robin
 hosts:
 - socket_address:
 address: 127.0.0.1
 port_value: 80
admin:
 access_log_path: "/dev/null"
 address:
 socket_address:
 address: 0.0.0.0
 port_value: 8081
複製代碼

具體的含義請參考爲 Envoy 開啓 TLS 驗證明戰

本文開頭提到 podman 建立的容器是 podman 的子進程,這個表述可能比較模糊,實際上 podman 由兩部分組成,一個是 podman CLI,還有一個是 container runtime,container runtime 由 conmon 來負責,主要包括監控、日誌、TTY 分配以及相似 out-of-memory 狀況的瑣事。也就是說,conmon 是全部容器的父進程。

conmon 須要去作全部 systemd 不作或者不想作的事情。即便 CRI-O 不直接使用 systemd 來管理容器,它也將容器分配到 sytemd 兼容的 cgroup 中,這樣常規的 systemd 工具好比 systemctl 就能夠看見容器資源使用狀況了。

$ podman ps

CONTAINER ID  IMAGE                                     COMMAND               CREATED             STATUS                 PORTS  NAMES
42762bf7d37a  docker.io/envoyproxy/envoy-alpine:latest  /docker-entrypoin...  About a minute ago  Up About a minute ago         hugo-envoy
f0204fdc9524  docker.io/library/nginx:alpine            nginx -g daemon o...  2 minutes ago       Up 2 minutes ago              hugo
複製代碼

對 cgroup 不熟的同窗,能夠參考下面這個系列:

零基礎的同窗建議按照上面的目錄從上到下打怪升級,祝你好運!

3. 部署前端代理

這個很簡單,直接建立容器就行了:

$ podman run -d --name front-envoy \
--add-host=hugo:10.88.0.10 \
-v /opt/hugo/front-envoy.yaml:/etc/envoy/envoy.yaml \
-v /etc/localtime:/etc/localtime \
-v /root/.acme.sh/yangcs.net:/root/.acme.sh/yangcs.net \
--net host envoyproxy/envoy
複製代碼

因爲沒辦法自動服務發現,須要經過參數 --add-host 手動添加 hosts 到容器中。envoy 的配置文件中是經過域名來添加 cluster 的,front-envoy.yaml 內容以下:

static_resources:
 listeners:
 - address:
 socket_address:
 address: 0.0.0.0
 port_value: 80
 filter_chains:
 - filters:
 - name: envoy.http_connection_manager
 config:
 codec_type: auto
 stat_prefix: ingress_http
 access_log:
 - name: envoy.file_access_log
 config:
 path: "/dev/stdout"
 route_config:
 virtual_hosts:
 - name: backend
 domains:
 - "*"
 routes:
 - match:
 prefix: "/"
 redirect:
 https_redirect: true
 response_code: "FOUND"
 http_filters:
 - name: envoy.router
 config: {}
 - address:
 socket_address:
 address: 0.0.0.0
 port_value: 443
 filter_chains:
 - filter_chain_match:
 server_names: ["yangcs.net", "www.yangcs.net"]
 tls_context:
 common_tls_context:
 alpn_protocols: h2
 tls_params:
 tls_maximum_protocol_version: TLSv1_3
 tls_certificates:
 - certificate_chain:
 filename: "/root/.acme.sh/yangcs.net/fullchain.cer"
 private_key:
 filename: "/root/.acme.sh/yangcs.net/yangcs.net.key"
 filters:
 - name: envoy.http_connection_manager
 config:
 codec_type: auto
 stat_prefix: ingress_http
 route_config:
 name: local_route
 virtual_hosts:
 - name: backend
 domains:
 - "yangcs.net"
 - "www.yangcs.net"
 routes:
 - match:
 prefix: "/admin"
 route:
 prefix_rewrite: "/"
 cluster: envoy-ui
 - match:
 prefix: "/"
 route:
 cluster: hugo
 response_headers_to_add:
 - header:
 key: "Strict-Transport-Security"
 value: "max-age=63072000; includeSubDomains; preload"
 http_filters:
 - name: envoy.router
 config: {}
 clusters:
 - name: hugo
 connect_timeout: 0.25s
 type: strict_dns
 lb_policy: round_robin
 http2_protocol_options: {}
 hosts:
 - socket_address:
 address: hugo
 port_value: 8080
admin:
 access_log_path: "/dev/null"
 address:
 socket_address:
 address: 0.0.0.0
 port_value: 8001
複製代碼

具體的含義請參考爲 Envoy 開啓 TLS 驗證明戰

如今就能夠經過公網域名訪問博客網站了,若是後續還有其餘應用,均可以參考第二節的步驟,而後從新建立前端代理,添加 --add-host參數。以個人網站 www.yangcs.net 爲例:

我好像透露了一些什麼不得了的東西,就此打住,你也不要說,你也不要問。

4. 開機自啓

因爲 podman 再也不使用 daemon 管理服務,--restart 參數被廢棄了,要想實現開機自動啓動容器,只能經過 systemd 來管理了。先建立 systemd 服務配置文件:

$ vim /etc/systemd/system/hugo_container.service

[Unit]
Description=Podman Hugo Service
After=network.target
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/podman start -a hugo
ExecStop=/usr/bin/podman stop -t 10 hugo
Restart=always

[Install]
WantedBy=multi-user.target
複製代碼
$ vim /etc/systemd/system/hugo-envoy_container.service

[Unit]
Description=Podman Hugo Sidecar Service
After=network.target
After=network-online.target
After=hugo_container.service

[Service]
Type=simple
ExecStart=/usr/bin/podman start -a hugo-envoy
ExecStop=/usr/bin/podman stop -t 10 hugo-envoy
Restart=always

[Install]
WantedBy=multi-user.target
複製代碼
$ vim /etc/systemd/system/front-envoy_container.service

[Unit]
Description=Podman Front Envoy Service
After=network.target
After=network-online.target
After=hugo_container.service hugo-envoy_container.service

[Service]
Type=simple
ExecStart=/usr/bin/podman start -a front-envoy
ExecStop=/usr/bin/podman stop -t 10 front-envoy
Restart=always

[Install]
WantedBy=multi-user.target
複製代碼

而後將以前中止以前建立的容器,注意:是中止,不是刪除!

$ podman stop $(podman ps -aq)
複製代碼

最後經過 systemd 服務啓動這些容器。

$ systemctl start hugo_container
$ systemctl start hugo-envoy_container
$ systemctl start front-envoy_container
複製代碼

設置開機自啓。

$ systemctl enable hugo_container
$ systemctl enable hugo-envoy_container
$ systemctl enable front-envoy_container
複製代碼

以後每次系統重啓後 systemd 都會自動啓動這個服務所對應的容器。

4. 總結

以上就是將博客從 Docker 遷移到 Podman 的全部變動操做,整體看下來仍是比較曲折,由於 Podman 是爲 Kubernetes 而設計的,而我要求過高了,就一個資源緊張的 vps,即不想上 Kubernetes,也不想上 etcd,既想搞 sidecar,又想搞自動服務發現,我能怎麼辦,我也很絕望啊,這個事怨不得 podman,爲了防止在你們內心留下 「podman 很差用」 的印象,特此聲明一下。啥都不想要,只能本身想辦法了~~

微信公衆號

掃一掃下面的二維碼關注微信公衆號,在公衆號中回覆◉加羣◉便可加入咱們的雲原生交流羣,和孫宏亮、張館長、陽明等大佬一塊兒探討雲原生技術

相關文章
相關標籤/搜索