我如何在 Ansible 中执行相当于组合 with_subelements 循环和 with_nested 循环的操作?

How do I do the equivalent of combining a with_subelements loop and a with_nested loop in Ansible?

我正在编写一个剧本来获取部署到 Mirantis 的 UCP/MKE 集群的服务列表,并检查该集群中的所有 Docker worker,每个外部 swarm 服务端口是打开。

该剧本从本地主机进行 API 调用,以获取部署到集群的广泛 JSON 服务对象,使用 jmespath 简化为名称、ID、端口。

作为另一个剧本,我的剧本在集群中的每个工作人员上运行 shell 命令以获取开放端口列表。

我想遍历每个服务的每个端口,并确认端口是否在每个工作节点上打开。

我的 services/ports 数据对象可能如下所示:

[
    {
        "ID": "aefgergergergergerg",
        "Name": "application1_service",
        "Ports": [
            [
                30950,
                "tcp"
            ],
            [
                30951,
                "tcp"
            ]
        ]
    },
    {
        "ID": "sdfthtrhrthrthrthrthtrh",
        "Name": "application2_service",
        "Ports": [
            [
                31190,
                "tcp"
            ]
        ]
    },
...
]

(通过 API 调用获得,可以使用 jmespath 查询进行简化:

'[?Endpoint.Ports].{ ID: ID, Name: Spec.Name, Ports: Endpoint.Ports[?contains(@.PublishMode,`ingress`)].[PublishedPort, PublishMode, Protocol] }'

我的工作人员的开放端口对象如下所示:

ok: [worker1] => {
    "msg": [
        "tcp:31557",
        "tcp:31501",
        "tcp:31556",
        "tcp:31500",
        "tcp:30231",
        "tcp:30230",
        "tcp:30651",
        "tcp:30650"
    ]
}
ok: [worker2] => {
    "msg": [
        "tcp:31557",
        "tcp:31501",
        "tcp:31556",
        "tcp:31500",
        "tcp:30231",
        "tcp:30230",
        "tcp:30651",
        "tcp:30650"
    ]
}
ok: [worker3] => {
    "msg": [
        "tcp:31557",
        "tcp:31501",
        "tcp:31556",
        "tcp:31500",
        "tcp:30231",
        "tcp:30230",
        "tcp:30651",
        "tcp:30650"
    ]
}

通过

获得
iptables -L DOCKER-INGRESS | awk -F ' {2,}' '( == "ACCEPT") && ( ~ /dpt/) {print }' | sed 's/ dpt//g')

在我的脑海中,我想将 with_subelements 循环(每个给定服务的端口)与 with_nested 循环(我的子元素作为第一个列表,我的开放端口作为嵌套列表),但我确信这不太可能。

这是我的剧本的相关部分(我删除了不相关的授权逻辑)

- name: Ensure secrets are in the required collections
  hosts: localhost
  gather_facts: false
  vars_files: vars.yaml

[SNIP]

    - name: "Get a list of services from https://{{ endpoint }}/services"
      ansible.builtin.uri:
        url: "https://{{ endpoint }}/services"
        body_format: json
        headers:
          Authorization: "Bearer {{ auth.json.auth_token }}"
        validate_certs: "{{ validate_ssl_certs | default('yes') }}"
      register: services

    - name: "Create a simplified JSON object of services and ports"
      ansible.builtin.set_fact:
        services_ports: "{{ services.json | json_query(jmesquery) }}"
      vars:
        jmesquery: "{{ jmesquery_services }}"

- name: See what ports are open on which workers
  hosts: workers
  gather_facts: false
  become: true
  vars_files: vars.yaml
  tasks:
    - name: Get the list of open ports
      shell: iptables -L DOCKER-INGRESS | awk -F ' {2,}' '( == "ACCEPT") && ( ~ /dpt/) {print }' | sed 's/ dpt//g'
      register: iptables_rules

    - name: debug
      debug:
        msg: "{{ iptables_rules.stdout_lines }}"

vars.yaml的相关位:

---
jmesquery_services: '[?Endpoint.Ports].{ ID: ID, Name: Spec.Name, Ports: Endpoint.Ports[?contains(@.PublishMode,`ingress`)].[PublishedPort, PublishMode, Protocol] }'

如何最好地针对每个工作人员上的每个开放端口检查每个端口的服务?

你在使用 subelements 查找插件的正确轨道上,你应该简单地遍历 service/port 对并检查 iptables_rules.stdout_lines.

中是否存在这样的端口

这是一个带有虚拟数据的示例剧本:

- hosts: localhost
  gather_facts: false
  become: false
  tasks:
    - name: check if all service ports are open
      # Loop over service/port pairs
      loop: "{{ lookup('subelements', services_ports, 'Ports') }}"
      # Set variables for clarity
      vars:
        service_name: "{{ item[0]['Name'] }}"
        iptables_port: "{{ item[1][1] ~ ':' ~ item[1][0] }}"
        iptables_port_exists: "{{ iptables_port in iptables_rules.stdout_lines }}"
      # Fail the module if port is not found
      failed_when: "not iptables_port_exists"
      # For demo, print out the service status
      debug:
        msg: "Service {{ service_name }} is {{ 'up' if iptables_port_exists else 'down' }} on port {{ iptables_port }}"
  # Example data
  vars:
    services_ports:
      - ID: aefgergergergergerg
        Name: application1_service
        Ports:
          - ["30950", "tcp"]
          - ["30951", "tcp"]
      - ID: sdfthtrhrthrthrthrthtrh
        Name: application2_service
        Ports:
          - ["31190", "tcp"]
    iptables_rules:
      stdout_lines: [
        "tcp:30950",
        "tcp:31190",
      ]

这会产生如下输出:

TASK [check if all service ports are open] *************************************
ok: [localhost] => (item=[{'ID': 'aefgergergergergerg', 'Name': 'application1_service'}, ['30950', 'tcp']]) => {
    "msg": "Service application1_service is up on port tcp:30950"
}
failed: [localhost] (item=[{'ID': 'aefgergergergergerg', 'Name': 'application1_service'}, ['30951', 'tcp']]) => {
    "msg": "Service application1_service is down on port tcp:30951"
}
ok: [localhost] => (item=[{'ID': 'sdfthtrhrthrthrthrthtrh', 'Name': 'application2_service'}, ['31190', 'tcp']]) => {
    "msg": "Service application2_service is up on port tcp:31190"
}
fatal: [localhost]: FAILED! => {"msg": "One or more items failed"}

PS! 我不熟悉 jmespath,但您可能需要对 worker 使用 hostvars['localhost']['services_ports'] 才能访问创建的变量localhost