点击蓝色“程序猿DD”关注我

回复“资源”获取独家整理的学习资料!

作者 | 米开朗基杨

来源 | 公众号「云原生实验室」

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 模型有很多优势,比如:

  • 系统管理员可以知道某个容器进程到底是谁启动的。

  • 如果利用 cgroup 对 podman 做一些限制,那么所有创建的容器都会被限制。

  • SD_NOTIFY : 如果将 podman 命令放入 systemd 单元文件中,容器进程可以通过 podman 返回通知,表明服务已准备好接收任务。

  • socket 激活 : 可以将连接的 socket 从 systemd 传递到 podman,并传递到容器进程以便使用它们。

废话不多说,下面我们直接进入实战环节,本文将手把手教你如何用 podman 来部署静态博客,并通过 Sidecar 模式将博客所在的容器加入到 Envoy mesh 之中。

方案架构

我的部署方案涉及到两层 Envoy:

  • 首先会有一个前端代理单独跑一个容器。前端代理的工作是给访问者提供一个入口,将来自外部的访问请求转发到具体的后端服务。

  • 其次,博客静态页面由 nginx 提供,同时以 Sidecar 模式运行一个 Envoy 容器,它与 nginx 共享 network nemspace

  • 所有的 Envoy 形成一个 mesh,然后在他们之间共享路由信息。

我之前写过一篇用 Docker 部署 hugo 静态博客并配置 HTTPS 证书的文章,本文采用的是相同的方案,只是将 docker 换成了 podman,具体参考。

部署 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 lsPOD 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 psCONTAINER 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 -aCONTAINER 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 -apCONTAINER 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 hugoUSER    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:40Zlabels:app: hugoname: hugo
spec:containers:- command:- nginx- -g- daemon off;env:- name: PATHvalue: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin- name: TERMvalue: xterm- name: HOSTNAME- name: containervalue: podman- name: NGINX_VERSIONvalue: 1.17.4- name: NJS_VERSIONvalue: 0.3.5- name: PKG_RELEASEvalue: "1"image: docker.io/library/nginx:alpinename: reverentkirchresources: {}securityContext:allowPrivilegeEscalation: truecapabilities: {}privileged: falsereadOnlyRootFilesystem: falseworkingDir: /
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.0port_value: 8080filter_chains:- filters:- name: envoy.http_connection_managerconfig:codec_type: autostat_prefix: ingress_httpaccess_log:- name: envoy.file_access_logconfig:path: "/dev/stdout"route_config:name: local_routevirtual_hosts:- name: servicedomains:- "*"routes:- match:prefix: "/"route:cluster: local_servicehttp_filters:- name: envoy.routerconfig: {}clusters:- name: local_serviceconnect_timeout: 0.25stype: strict_dnslb_policy: round_robinhosts:- socket_address:address: 127.0.0.1port_value: 80
admin:access_log_path: "/dev/null"address:socket_address:address: 0.0.0.0port_value: 8081

具体的含义请参考。

本文开头提到 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 psCONTAINER 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 不熟的同学,可以参考下面这个系列:

零基础的同学建议按照上面的目录从上到下打怪升级,祝你好运!

部署前端代理

这个很简单,直接创建容器就好了:

$ 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.0port_value: 80filter_chains:- filters:- name: envoy.http_connection_managerconfig:codec_type: autostat_prefix: ingress_httpaccess_log:- name: envoy.file_access_logconfig:path: "/dev/stdout"route_config:virtual_hosts:- name: backenddomains:- "*"routes:- match:prefix: "/"redirect:https_redirect: trueresponse_code: "FOUND"http_filters:- name: envoy.routerconfig: {}- address:socket_address:address: 0.0.0.0port_value: 443filter_chains:- filter_chain_match:server_names: ["yangcs.net", "www.yangcs.net"]tls_context:common_tls_context:alpn_protocols: h2tls_params:tls_maximum_protocol_version: TLSv1_3tls_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_managerconfig:codec_type: autostat_prefix: ingress_httproute_config:name: local_routevirtual_hosts:- name: backenddomains:- "yangcs.net"- "www.yangcs.net"routes:- match:prefix: "/admin"route:prefix_rewrite: "/"cluster: envoy-ui- match:prefix: "/"route:cluster: hugoresponse_headers_to_add:- header:key: "Strict-Transport-Security"value: "max-age=63072000; includeSubDomains; preload"http_filters:- name: envoy.routerconfig: {}clusters:- name: hugoconnect_timeout: 0.25stype: strict_dnslb_policy: round_robinhttp2_protocol_options: {}hosts:- socket_address:address: hugoport_value: 8080
admin:access_log_path: "/dev/null"address:socket_address:address: 0.0.0.0port_value: 8001

具体的含义请参考。

现在就可以通过公网域名访问博客网站了,如果后续还有其他应用,都可以参考第二节的步骤,然后重新创建前端代理,添加 --add-host参数。以我的网站 https://www.yangcs.net 为例:

我好像透露了一些什么不得了的东西,就此打住,你也不要说,你也不要问。

开机自启

由于 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 都会自动启动这个服务所对应的容器。

总结

以上就是将博客从 Docker 迁移到 Podman 的所有变更操作,总体看下来还是比较曲折,因为 Podman 是为 Kubernetes 而设计的,而我要求太高了,就一个资源紧张的 vps,即不想上 Kubernetes,也不想上 etcd,既想搞 sidecar,又想搞自动服务发现,我能怎么办,我也很绝望啊,这个事怨不得 podman,为了防止在大家心里留下 “podman 不好用” 的印象,特此声明一下。啥都不想要,只能自己想办法了~~

-互动-

那么,对于Docker和Podman,你怎么看呢?

欢迎评论区交流

本文通过OpenWrite的免费Markdown转换工具发布

-END-

留言交流不过瘾

关注我,回复“加群”加入各种主题讨论群

朕已阅 

Docker 大势已去,Podman 万岁相关推荐

  1. Docker Vs Podman

    翻译自 Chetansingh 2020年4月24日的博文<Docker Vs Podman> [1] 容器化的一场全新革命是从 Docker 开始的,Docker 的守护进程管理着所有的 ...

  2. 在openEuler 21.9安装自带的容器软件Docker、podman、skopeo

    openEuler 21.9上的Docker.podman.Skopeo版本都偏低. 此外没有自带buildah和cri-o. 不知道下一个版本情况会不会好点. [root@openeuler iso ...

  3. [转]Docker 大势已去,Podman 即将崛起

    Podman Podman 什么是Podman? Podman和Docker的主要区别是什么? Podman的使用与docker有什么区别? Podman 常用命令 容器 镜像 部署 Podman P ...

  4. Docker 大势已去,Podman 即将崛起

    今日推荐 推荐一个 Java 接口快速开发框架干掉Random:这个类已经成为获取随机数的王者Docker + Intellij IDEA,提升 10 倍生产力!笑出腹肌的注释,都是被代码耽误的诗人! ...

  5. Docker、Podman 容器“扫盲“ 学习笔记【与云原生的故事】

    [摘要] 笔记内容:由理论和具体docker常用操作构成.这篇博文笔记的定位是:温习,查阅,不适合新手学习.你拥有青春的时候,就要感受它,不要虚掷你的黄金时代,不要去倾听... 写在前面 笔记内容:由 ...

  6. Docker、Podman 容器“扫盲“ 学习笔记

    写在前面 之前只是做毕业设计时,接触一点,用DockFile在阿里云上部署了毕设.后来docker端口问题,机器被植入木马,拉去挖矿,cup天天爆满,百度半天删了好多文件不管用,后来恢复镜像了,之后在 ...

  7. Docker学习总结(65)—— 容器引擎 Docker 与 Podman 的详细对比分析

    一.什么是 Linux 容器? Linux 容器是由 Linux 内核所提供的具有特定隔离功能的进程,Linux 容器技术能够让你对应用及其整个运行时环境(包括全部所需文件)一起进行打包或隔离.从而让 ...

  8. Podman又是什么新技术?它和Docker有啥区别?

    容器编排工具作为当今最重要的Web开发技术之一,众多强者都在尝试争夺这一行业的主导地位. Podman是RedHat的一款产品,旨在使用类似于Kubernetes的方法来构建.管理和运行容器,作为一款 ...

  9. Docker/Podman使用入门---从容器构建镜像 提交镜像到服务器UCloud dockerhub

    文章目录 1.docker commit 提交镜像命令 2.将镜像提交到UCloud服务器 step1: 先在UCloud服务器上面,创建镜像仓库 step2: 登录UCloud镜像仓库 step3: ...

最新文章

  1. Linux下安装jdk(xxx.rpm,非xxx.tar.gz,请注意!)过程
  2. HTML5之article元素与section元素之间的区别?
  3. 深度学习笔记第二门课 改善深层神经网络 第二周:优化算法
  4. 1万条数据大概占多大空间_9月漫画数据月报丨多平台评论数,收藏数较上月大幅下降...
  5. 牛客多校2 - Interval(网格图最大流转换为对偶图最短路)
  6. android studio crashlytics,完美解决Android Studio集成crashlytics后无法编译的问题
  7. Caffe查看每一层学习出来的pattern
  8. flash游戏代码_Flash正式宣告死亡,4399游戏还能玩么——从没有所谓的千秋万代...
  9. java 创建日程到期提醒_晓日程 微信日历加桌面日历,规划时间,掌握未来
  10. html5 audio js控制进度,HTML5 audio标签使用js进行播放控制实例
  11. java.lang.ClassNotFoundException: org.springframework.web.util.WebAppRootListener
  12. 不使用服务器控件的ASP.NET
  13. 简单理解Zookeeper的Leader选举
  14. (转)2017中国互联网证券年度报告
  15. 数据结构 实验五(银行叫号系统)
  16. esp32-cam拍照上传,微信小程序照片显示
  17. 结构体Sqlist L与Sqlist L的区别
  18. 架构设计实践五部曲(一):架构与架构图
  19. java线上文件图片资源存储方案,定时清理垃圾文件
  20. 华为路由器接口如何区分_华为路由的线路输出的两种不同方法简介

热门文章

  1. cve-2019-11076 Cribl UI 1.5.0 未授权命令执行漏洞分析
  2. linux c 获取时间戳
  3. 安全产品研发与落地的一些方法与思考
  4. linux mount挂载文件夹设置权限
  5. wifi密码破解与攻击
  6. linux报错 find: missing argument to `-exec'
  7. socket的半包,粘包与分包的问题
  8. ring0下的 fs:[124]
  9. vs2010下release版本调试设置
  10. C语言通讯录管理系统