使用Ansible标签功能并行执行任务

首先

我想從之前為LOB Advent Calendar撰寫的文章「使用Ansible的模板功能生成管理GCP的Terraform配置」中派生,介紹如何使用Ansible的標籤功能來並行處理沒有依賴關係的任務。雖然在我們想要並行執行Playbook任務的情況下,有人認為應該將Playbook分開,但請容許我在這方面不同意見。

简而言之

这次我们来试试为每个环境准备100个pub / sub实例的情况。这是Ansible并发执行的示例,为了增加任务数,我故意从一个模板生成了100个文件。

目录结构

以下是需要根据上一篇文章进行额外修改的模板文件,有两个选项:

    • pub/sub 用のテンプレート ( pubsub.tf.j2 ) を新規で追加

 

    dev.yml, stg.yml, prd.yml のベースとなるテンプレート ( env.yml.j2 ) の追記

最终,以下文件将被放置在目录中。

.
├── ansible.cfg (ansibleの設定。リトライファイルの生成を抑制する目的で配置)
├── pre.yml (dev.yml, stg.yml, prd.ymlを生成するためのPlaybook)
├── dev.yml (devの設定を生成するためのPlaybook)
├── stg.yml (stgの設定を生成するためのPlaybook)
├── prd.yml (prdの設定を生成するためのPlaybook)
├── dev
│   └── gcp
│       └── terraform (dev.ymlを実行すると生成される設定)
│           ├── cloudsql.tf
│           ├── credential.json
│           ├── providor.tf
│           ├── pubsub-001.tf
│           ├── pubsub-002.tf
│           ├──  ........
│           └── pubsub-100.tf
├── stg
│   └── gcp
│       └── terraform (stg.ymlを実行すると生成される設定)
│           ├── cloudsql.tf
│           ├── credential.json
│           ├── providor.tf
│           ├── pubsub-001.tf
│           ├── pubsub-002.tf
│           ├──  ........
│           └── pubsub-100.tf
├── prd
│   └── gcp
│       └── terraform (prd.ymlを実行すると生成される設定)
│           ├── cloudsql.tf
│           ├── credential.json
│           ├── providor.tf
│           ├── pubsub-001.tf
│           ├── pubsub-002.tf
│           ├──  ........
│           └── pubsub-100.tf
└── template 
    ├── env.yml.j2 (dev.yml, stg.yml, prd.ymlのベースとなるテンプレート)
    └── gcp
        └── terraform
            ├── dev
            │   └── credential.json.j2 (サービスアカウントのクレデンシャル(dev))
            ├── stg
            │   └── credential.json.j2 (サービスアカウントのクレデンシャル(stg))
            ├── prd
            │   └── credential.json.j2 (サービスアカウントのクレデンシャル(prd))
            └── share
                ├── cloudsql.tf.j2 (Terraform で cloud sql を生成するための設定)
                ├── providor.tf.j2 (Terraform で GCP を使うための設定)
                └── pubsub.tf.j2   (Terraform で pubsub を生成するための設定)

安装

让我们先安装 yq、datamash和parallel这些工具,以便稍后使用。

    • yq は Ansible Playbook の YAML ファイルからタグの属性を抽出するために使用します。

 

    • datamash は CSV の行と列を入れ替えるために使用します。

 

    parallel は Ansible Playbook を並列実行するために使用します。

如果是Mac的话,你可以轻松地通过以下步骤进行操作。

$ brew install yq datamash parallel

创建用于生成Terraform配置的模板

Terraform 的模板如下,请将其放置在指定的位置。

resource "google_pubsub_topic" "sample_topic_{{index}}" {
  name = "sample_topic_{{index}}"
}

resource "google_pubsub_subscription" "sample_subscription_{{index}}" {
  name  = "sample_subscription_{{index}}"
  topic = "sample_topic_{{index}}"
}

添加 Ansible 的任务来生成 Terraform 的配置

在 Ansible Playbook 的模板中添加一个生成 pub/sub 的任务。
可以使用以下命令进行添加。

$ for i in `seq -w 1 100` ; do
cat << EOF
    - name: {{env}}/gcp/terraform/pubsub-${i}.tf
      tags: {{env}}/gcp/terraform/pubsub-${i}.tf
      template:
        src: ./template/gcp/terraform/share/pubsub.tf.j2
        dest: ./{{env}}/gcp/terraform/pubsub-${i}.tf
      vars:
        index: ${i}

EOF
done >> ./template/env.yml.j2

添加后, 应该在env.yml.j2文件中追加以下内容。

 ... 省略 ...
    - name: {{env}}/gcp/terraform/pubsub-001.tf
      tags: {{env}}/gcp/terraform/pubsub-001.tf
      template:
        src: ./template/gcp/terraform/share/pubsub.tf.j2
        dest: ./{{env}}/gcp/terraform/pubsub-001.tf
      vars:
        index: 001

    - name: {{env}}/gcp/terraform/pubsub-002.tf
      tags: {{env}}/gcp/terraform/pubsub-002.tf
      template:
        src: ./template/gcp/terraform/share/pubsub.tf.j2
        dest: ./{{env}}/gcp/terraform/pubsub-002.tf
      vars:
        index: 002

 ... 省略 ...

生成 Ansible Playbook.

执行Ansible Playbook中的pre.yml,以生成每个环境的Playbook(dev.yml、stg.yml、prd.yml)。

% ansible-playbook pre.yml
PLAY [localhost] *********************************************************************************************************************************

TASK [./{{ env }}.yml] ***************************************************************************************************************************
ok: [localhost] => (item={'env': 'dev', 'gcp': {'terraform': {'cloudsql': {'default': {'tier': 'db-n1-standard-1', 'disk_size': 10}}}}})
ok: [localhost] => (item={'env': 'stg', 'gcp': {'terraform': {'cloudsql': {'default': {'tier': 'db-n1-standard-1', 'disk_size': 100}}}}})
ok: [localhost] => (item={'env': 'prd', 'gcp': {'terraform': {'cloudsql': {'default': {'tier': 'db-n1-standard-8', 'disk_size': 100}}}}})

PLAY RECAP ***************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0

用 Ansible 逐个生成 Terraform 的配置。

那么,现在我们来执行 Ansible Playbook 的 dev.yml 文件,一次生成一个 Terraform 的配置文件。为了参考,我也会在下面列出我的笔记本电脑的规格。

MacBook Pro 13-inch 2017
CPU    : 2.3GHzデュアルコアIntel Core i5
Memory : 16 GB

我试着执行Playbook来生成开发环境的配置,但生成100个文件大约需要43秒。希望能稍微缩短一下时间。

% time ansible-playbook dev.yml
PLAY [localhost] **************************************************************************************************

TASK [dev/gcp/terraform/pubsub-001.tf] ****************************************************************************
ok: [localhost]

.....

TASK [dev/gcp/terraform/pubsub-100.tf] ****************************************************************************
ok: [localhost]

PLAY RECAP ********************************************************************************************************
localhost                  : ok=103  changed=0    unreachable=0    failed=0

30.18s user 11.98s system 97% cpu 43.449 total

在Ansible中,使用4个并行运行的线程来生成Terraform的配置。

现在我们将尝试使用Ansible的标签功能来分割执行刚刚执行过的任务。虽然稍微有点复杂,但流程如下。

    1. 从Ansible Playbook中提取标签

 

    1. 将标签用逗号分隔成四个,然后换行(转换为四列的CSV格式)

 

    1. 交换行和列(转换为四行的CSV文件)

 

    逐行将CSV文件作为参数传递给ansible命令,并使用parallel命令并行执行

由於上述的說明可能不太容易理解,我們會在執行命令並確認的過程中繼續進行。

从 Ansible 的 Playbook 中提取标签

使用最初安装的 yq 命令来提取标签。

$ yq r dev.yaml [0].tasks[*].tags
- dev/gcp/terraform/credential.tf
- dev/gcp/terraform/providor.tf
- dev/gcp/terraform/cloudsql.tf
- dev/gcp/terraform/pubsub-001.tf
- dev/gcp/terraform/pubsub-002.tf
... 省略 ...

接下来,你可以根据自己的喜好,选择任意的方法来移除开头的破折号。

$ yq r dev.yml [0].tasks[*].tags | grep pubsub | perl -pe 's/^- (.*)/$1/'
dev/gcp/terraform/pubsub-001.tf
dev/gcp/terraform/pubsub-002.tf
... 省略 ...

将标签以逗号为分隔符排列成4个,并换行(转换为4列的CSV格式)。

不讨论一句话内容,只需要执行以下操作来分割成这样。

$ yq r dev.yml [0].tasks[*].tags | grep pubsub | perl -pe 's/^- (.*)/$1/' |\
  perl -pe "s/\n$/,/ if $.%4"
dev/gcp/terraform/pubsub-001.tf,dev/gcp/terraform/pubsub-002.tf,dev/gcp/terraform/pubsub-003.tf,dev/gcp/terraform/pubsub-004.tf
dev/gcp/terraform/pubsub-005.tf,dev/gcp/terraform/pubsub-006.tf,dev/gcp/terraform/pubsub-007.tf,dev/gcp/terraform/pubsub-008.tf
dev/gcp/terraform/pubsub-009.tf,dev/gcp/terraform/pubsub-010.tf,dev/gcp/terraform/pubsub-011.tf,dev/gcp/terraform/pubsub-012.tf
dev/gcp/terraform/pubsub-013.tf,dev/gcp/terraform/pubsub-014.tf,dev/gcp/terraform/pubsub-015.tf,dev/gcp/terraform/pubsub-016.tf
dev/gcp/terraform/pubsub-017.tf,dev/gcp/terraform/pubsub-018.tf,dev/gcp/terraform/pubsub-019.tf,dev/gcp/terraform/pubsub-020.tf
... 省略 ...

将行和列进行交换(转换为一个包含4行的CSV文件)。

最终目标是要以4列并行执行命令,因此总共需要4行文本。
现在,输出的CSV文本有4列,所以我们需要将行和列互换,转换为4行文本。
如果要交换CSV的行和列,可以使用一个方便的工具叫作datamash来实现。
另外,行末的逗号是不必要的,我们将其删除。

执行后会输出以下内容。

$ yq r dev.yml [0].tasks[*].tags | grep pubsub | perl -pe 's/^- (.*)/$1/' |\
  perl -pe "s/\n$/,/ if $.%4" |\
  datamash --no-strict -t ',' transpose |\
  sed -e 's/,$//'
dev/gcp/terraform/pubsub-001.tf,dev/gcp/terraform/pubsub-005.tf,dev/gcp/terraform/pubsub-009.tf,dev/gcp/terraform/ ...(省略)...
dev/gcp/terraform/pubsub-002.tf,dev/gcp/terraform/pubsub-006.tf,dev/gcp/terraform/pubsub-010.tf,dev/gcp/terraform/ ...(省略)...
dev/gcp/terraform/pubsub-003.tf,dev/gcp/terraform/pubsub-007.tf,dev/gcp/terraform/pubsub-011.tf,dev/gcp/terraform/ ...(省略)...
dev/gcp/terraform/pubsub-004.tf,dev/gcp/terraform/pubsub-008.tf,dev/gcp/terraform/pubsub-012.tf,dev/gcp/terraform/ ...(省略)...
... 省略 ...

将最后提取的4行标签列表作为参数输入到parallel命令中,并以4个并行来执行。大约需要24秒的时间。

$ time (\
  yq r dev.yml [0].tasks[*].tags | grep pubsub | perl -pe 's/^- (.*)/$1/' |\
  perl -pe "s/\n$/,/ if $.%4" |\
  datamash --no-strict -t ',' transpose |\
  sed -e 's/,$//' |\
  parallel -u -P 4 ansible-playbook -t {} dev.yml )

50.00s user 18.36s system 288% cpu 23.688 total

总结

使用Ansible并行执行和不并行执行之间进行比较时,处理时间大约减少了一半。
由于本次执行的机器有2个核心,因此可以如预期地加速。
原本,Ansible的标签功能存在的目的是用于仅执行特定任务,但在没有任务依赖关系等情况下,可以并行执行任务并加快速度,无需拆分Playbook。如果方便的话,请参考一下。

如果不同时执行的话

30.18s user 11.98s system 97% cpu 43.449 total

如果同时进行

50.00s user 18.36s system 288% cpu 23.688 total

以上就是。

广告
将在 10 秒后关闭
bannerAds