使用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的标签功能来分割执行刚刚执行过的任务。虽然稍微有点复杂,但流程如下。
-
- 从Ansible Playbook中提取标签
-
- 将标签用逗号分隔成四个,然后换行(转换为四列的CSV格式)
-
- 交换行和列(转换为四行的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
以上就是。