, ,

技巧:Podman 卷和 Ansible 模板幂等性

任何对 Ansible 有点了解的人都会证明,维护幂等性是稳定自动化的关键秘诀。没有幂等性,几乎不可能检测到漂移和/或可预测地管理状态更改。同样,任何超出完全初学者 Podman 用户的人都会知道,定义和使用卷是必不可少的操作。

现在的问题是:在 Ansible 世界中,template 模块是用于部署和持续管理远程文件内容的重要工具。但是,如果您尝试使用模板来自动化 podman 卷内容管理,您将很快意识到一个主要的缺点

Ansible 会因为用户命名空间而完全卡住。

注意:如果您还不熟悉用户命名空间,我建议您阅读 Dan 的“在 Podman 无根容器中使用文件和设备”。它要么让您大开眼界,要么让您更加困惑。如果是后者,我建议您在继续之前阅读我的“用简单的英语解释无根 Podman 用户命名空间”文章。

例如,考虑下面的剧本 ./local_1.yml。它旨在部署一个 Web 服务器容器,不映射主机用户到 root 用户userns: nomap)。这种缺乏 user -> root 映射旨在保护主机。如果进程从容器中逃逸,它将无法访问主机用户的文件或进程。请忽略对 localhost 的定位,这仅仅是为了演示目的。但请注意明确管理卷和配置文件所有权的任务

./local_1.yml

---

- hosts: localhost
  gather_facts: true
  gather_subset: user
  become: false
  tasks:
    - name: Config volume for nginx container
      containers.podman.podman_volume:
        name: nginx_confd
        options:
          - o=uid=0,gid=0
        state: present
      register: vresult

    - name: Deploy nginx configuration
      template:
        src: default.conf.j2
        dest: >-
         {{ vresult.volume.Mountpoint }}/default.conf
        mode: u=rw,g=r,o=r
        owner: 0
        group: 0

    - name: Run nginx container
      containers.podman.podman_container:
        name: webserver
        image: nginx
        publish: ["8080:80"]
        userns: nomap
        volume:
          - >-
            {{ vresult.volume.Name }}:/etc/nginx/conf.d:Z,U
        recreate: true
        state: started

如果您还没有安装,请确保安装 podman 集合

$ ansible-galaxy collection install containers.podman

此外,在运行剧本之前,请确保您获取内置默认配置的副本。示例可以使用它作为配置模板的存根,因此无需实际在其中添加任何 jinja2 元素

$ mkdir templates
$ podman run -it --rm nginx \
    cat /etc/nginx/conf.d/default.conf \
    > ./templates/default.conf.j2

最后,当剧本以 ansible-playbook -i localhost, -l localhost -c local local_1.yml 的形式应用时,结果应类似于

FAILED! => {"changed": false, "checksum": "...", "mode": "0644",
"msg": "chown failed: [Errno 1] Operation not permitted:
.../nginx_confd/_data/default.conf" ...}

一种可能很明显的解决方法可能是简单地为用户授予 sudo 访问权限,在任务中使用 become: true,并根据一些硬编码的计算手动偏移 ID。但在您这样做之前,请仔细考虑。对于快速/脏的剧本来说,这可能没问题,但它肯定无法扩展。

这完全是因为全局命名空间执行上下文。Ansible 以普通用户的身份在“远程”主机上执行任务,没有 sudo 访问权限(become: false)。此外,template 模块试图写入一个文件(Ansible 假设)由 UID/GID 拥有。而实际上它应该由某个 UID/GID 拥有,该 UID/GID 从 $UID/$GID(由 /etc/subuid/etc/subgid 确定)偏移。更糟糕的是,Ansible 将永远无法告诉操作员任何 default.conf 更改,也无法触发相关的通知任务,例如重新启动容器。因此,从几乎所有方面来说,这都是不好的。太糟糕了!

对自动生成的、低级系统细节(如 ID)的管理完全破坏了 Ansible 的“通用”性质(按规模)。它要求它操纵超出其预期范围的方面。将此视为并非牵强附会,但完全有效的 /etc/subuid 内容

...cut...
fred:100000:65536
fred:165536:1024
fred:166560:131072
wilma:100000:999999
...cut...

如果还不明显:Ansbile 永远不应该直接与 subuid/subgid 映射纠缠,这与它不应该直接操纵 /etc/passwd/etc/group 的原因完全相同:这些是 OS 和系统实现细节,它们可能会以意想不到的方式发生变化。想想用户命名空间迁移到身份提供者(如 FreeIPAActive Directory)的情况,然后怎么办,丢弃所有剧本吗?

幸运的是,有一种方法可以解决这个问题,尽管它有点“hacky”:以一种对 Ansible 几乎透明的方式抽象掉用户命名空间机制。关键是在 Ansible 创建的远程进程链中插入一个 podman unshare 命令,从 python 开始。这可以通过一个简单的包装脚本非常简单地完成

./files/podman_unshare_wrapper.sh

#!/bin/sh

exec /usr/bin/podman unshare $(type -p python3) "$@"

如以下 ./local_2.yml 示例剧本所示,部署包装器很简单,请确保在本地 ./files 子目录中创建它。然后,可以使用 Ansible copy 模块的默认行为来创建任何缺少的远程目标目录。将包装器投入使用也同样简单。临时覆盖“远程” python 解释器位置。然后,template 模块可以通过包装器正确处理用户命名空间转换,以设置预期的文件所有权

./local_2.yml

---

- hosts: localhost
  gather_facts: true
  gather_subset: user
  become: false
  tasks:
    - name: Config volume for nginx container
      containers.podman.podman_volume:
        name: nginx_confd
        options:
          - o=uid=0,gid=0
        state: present
      register: vresult

    - name: Deploy podman unshare wrapper
      copy:
        src: podman_unshare_wrapper.sh
        dest: >-
          {{ ansible_user_dir }}/.local/bin/
        mode: u=rxw,g=rx,o=rx

    - name: Deploy nginx configuration
      vars:
        ansible_python_interpreter: >-
          {{ ansible_user_dir }}/.local/bin/podman_unshare_wrapper.sh
      template:
        src: default.conf.j2
        dest: >-
         {{ vresult.volume.Mountpoint }}/default.conf
        mode: u=rw,g=r,o=r
        owner: 0
        group: 0

    - name: Run nginx container
      containers.podman.podman_container:
        name: webserver
        image: nginx
        publish: ["8080:80"]
        userns: nomap
        volume:
          - >-
            {{ vresult.volume.Name }}:/etc/nginx/conf.d:Z,U
        recreate: true
        state: started

尝试以与之前完全相同的方式运行该剧本,ansible-playbook -i localhost, -l localhost -c local local_2.yml。您应该发现它成功完成并启动了一个工作的 nginx 容器(侦听 http://localhost:8080)。此外,您可以使用以下命令从主机验证正确的文件所有权

$ vol=$(podman volume inspect -f '{{.Mountpoint}}' nginx_confd)
$ podman unshare ls -lan "$vol"
total 4
drwxr-xr-x. 2 0 0   26 Feb  6 15:23 .
drwx------. 3 0 0   19 Feb  6 15:23 ..
-rw-r--r--. 1 0 0 1093 Feb  6 15:23 default.conf

假设一切都正常,Ansible template 模块现在有了新的超能力!也许最好的部分是,模板任务的这个版本将在内容漂移或需要运行处理程序时提供完整的更改详细信息。继续修改本地存根模板文件。然后再次运行剧本,注意 changed 状态,并使用以下命令查看它是否干净地更新了文件

$ podman exec -it webserver cat /etc/nginx/conf.d/default.conf

诚然,这个包装脚本绝对是一个 hack,所以它可能无法永远起作用,也不适用于需要它的每个 Ansible 模块。但至少现在,希望这些示例已经突出了涉及的组件和关键操作。因此,维护 hack 应该相对容易。在所有情况下,有了这些新知识,我希望您能够将其应用到将来可能需要/受益于它的任何剧本中。

“技巧:Podman 卷和 Ansible 模板幂等性”的 2 个回复

  1. David Avatar

    为什么不使用来自 containers.podman 的 podman_unshare become 方法?https://docs.ansible.org.cn/ansible/latest/collections/containers/podman/podman_unshare_become.html

  2. Chris Evich Avatar

    真棒!我以前不知道那个插件(看起来它是在 1.9.0 中添加的)。是的,如果可用的话,我会使用它而不是我的 hack。感谢分享 😀

发表评论

订阅

使用您的电子邮件地址注册以接收来自本网站的电子邮件更新。


类别


搜索