使用Ansible Molecule的代理驱动程序,通过terraform创建用于测试的AWS EC2实例

问题:

使用Ansible Molecule进行Playbook测试时,如果在云上进行测试,使用Terraform有以下好处。

    • 様々なクラウドに対応可能

 

    冪等性はterraformにて対応

在这里以 AWS EC2 环境为例进行设置的尝试。

环境: 形成一个人或物所处的自然和社会条件,包括空气、水、土壤、气候、动植物等因素。

    • macOS 10.15.7

 

    • Python(pyenv) 3.8.2

 

    • ansible 2.10.2

 

    • molecule 3.1.5

 

    • Terraform v0.13.4

 

    • yq 2.10.1 (オプション)

 

    • vm接続に使用するssh-key ~/.ssh/id_rsa, ~/.ssh/id_rsa.pub

 

    環境変数 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY

主要文件:

.
├── molecule
│   └── default
│       ├── common.tf.yml             # terraform用ec2設定ファイル1
│       ├── ins01.tf.yml              # terraform用ec2設定ファイル2
│       │ 
│       ├── (common.tf.json)          # common.tf.ymlから生成
│       ├── (ins01.tf.json)           # ins01.tf.ymlから生成
│       │ 
│       ├── (instance_conf-ins01.yml) # terraform作成
│       │                             # moleculeに連携するインスタンスへの接続情報
│       │ 
│       ├── (terraform.tfstate)       # terraform作成。ステータス情報
│       │ 
│       ├── molecule.yml              # molecule定義情報
│       ├── create.yml                # molecule vm作成playbook
│       ├── prepare.yml               # molecule vm事前準備playbook
│       ├── converge.yml              # molecule vmへの変更反映playbook
│       ├── verify.yml                # molecule vm検証playbook
│       └── destroy.yml               # molecule vm削除playbook
└── tasks
    └── main.yml

以下是样本代码:
https://github.com/hiroyuki-onodera/molecule-delegated-terraform-ec2

第一章:通过Terraform管理AWS EC2

在事前准备阶段,在IAM中添加具有AmazonEC2完全访问权限的用户。

在AWS控制台中,通过Identity and Access Management(IAM)> 用户菜单来添加用户。

在这里,我们采取以下的方式

    • AWS アクセスの種類: プログラムによるアクセス – アクセスキーを使用

 

    • アクセス権限の境界: アクセス権限の境界が設定されていません

 

    • アクセス許可の設定

既存のポリシーを直接アタッチ

AWS 管理ポリシー AmazonEC2FullAccess

将获取到的访问密钥ID和秘密访问密钥设置为环境变量AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY。

在Bash的情况下的例子

$ cat <<EOC >> ~/.bash_profile
export AWS_ACCESS_KEY_ID="...................."
export AWS_SECRET_ACCESS_KEY="........................................"
EOC
$ . ~/.bash_profile

使用 Terraform 进行提供程序和网络定义。

    • .tfフォーマットは他のユーティリティとの連携などが困難

 

    • .tf.jsonフォーマットは人が直接使用するには難しい

 

    • ここでは、.tf.jsonをYAMLで表記したファイルにてec2設定を行い、terraform使用前にjson化

 

    • 最初から.tfフォーマットや.tf.jsonフォーマットで記述、管理するならばYAML管理は不要

 

    • common.tf.yml は、providerやネットワーク定義など個別のインスタンスに1:1で対応しない設定をまとめたもの。

VPC EC2定義の大部分は以下から引用させていただいています。ありがとうございます。

TerraformでVPC・EC2インスタンスを構築してssh接続する
https://qiita.com/kou_pg_0131/items/45cdde3d27bd75f1bfd5

---
terraform:
  required_providers:
    aws:
      source: hashicorp/aws
      version: "~> 3.0"
provider:
  aws:
    region: ap-northeast-1
data:
  aws_ami:
    ami01:
      most_recent: true
      owners:
      - amazon
      filter:
      - name: architecture
        values:
        - x86_64
      - name: root-device-type
        values:
        - ebs
      - name: name
        values:
        - amzn2-ami-hvm-*
      - name: virtualization-type
        values:
        - hvm
      - name: block-device-mapping.volume-type
        values:
        - gp2
      - name: state
        values:
        - available
resource:
  aws_vpc:
    vpc01:
      cidr_block: 10.0.0.0/16
      enable_dns_support: true
      enable_dns_hostnames: true
  aws_subnet:
    subnet01:
      cidr_block: 10.0.1.0/24
      availability_zone: ap-northeast-1a
      vpc_id: "${aws_vpc.vpc01.id}"
      map_public_ip_on_launch: true
  aws_internet_gateway:
    internet_gateway01:
      vpc_id: "${aws_vpc.vpc01.id}"
  aws_route_table:
    route_table01:
      vpc_id: "${aws_vpc.vpc01.id}"
  aws_route:
    route01:
      gateway_id: "${aws_internet_gateway.internet_gateway01.id}"
      route_table_id: "${aws_route_table.route_table01.id}"
      destination_cidr_block: 0.0.0.0/0
  aws_route_table_association:
    route_table_association01:
      subnet_id: "${aws_subnet.subnet01.id}"
      route_table_id: "${aws_route_table.route_table01.id}"
  aws_security_group:
    security_group01:
      name: security_group01
      vpc_id: "${aws_vpc.vpc01.id}"
  aws_security_group_rule:
    in_ssh:
      security_group_id: "${aws_security_group.security_group01.id}"
      type: ingress
      cidr_blocks:
      - 0.0.0.0/0
      from_port: 22
      to_port: 22
      protocol: tcp
    in_icmp:
      security_group_id: "${aws_security_group.security_group01.id}"
      type: ingress
      cidr_blocks:
      - 0.0.0.0/0
      from_port: -1
      to_port: -1
      protocol: icmp
    out_all:
      security_group_id: "${aws_security_group.security_group01.id}"
      type: egress
      cidr_blocks:
      - 0.0.0.0/0
      from_port: 0
      to_port: 0
      protocol: "-1"
  aws_key_pair:
    key_pair01:
      key_name: key_pair01
      public_key: ${file("~/.ssh/id_rsa.pub")}

通过Terraform对实例进行特定定义。

    • インスタンス名.tf.ymlは、インスタンスに1:1で対応する資源をまとめたもの

 

    • インスタンス毎に1ファイルの想定

 

    • j2などテンプレートエンジンを用いての作成は、どの範囲までユーザー指定とするかなど環境、プロジェクト依存の項目が多く、不必要に複雑化する為に断念。シンプルにこのファイル内での直接指定による設定としている。

 

    • 作成したインスタンスへの接続情報はlocal_file:リソースを用いてMoleculeに連携する

 

    ユーザー利便を考慮してsshログイン方法をoutputにて表示
---
resource:
  aws_instance:
    ins01:
      ami: "${data.aws_ami.ami01.image_id}"
      vpc_security_group_ids:
      - "${aws_security_group.security_group01.id}"
      subnet_id: "${aws_subnet.subnet01.id}"
      key_name: "${aws_key_pair.key_pair01.id}"
      instance_type: t2.micro
  aws_eip:
    eip-ins01:
      instance: "${aws_instance.ins01.id}"
      vpc: true
  local_file:
    instance_conf-ins01:
      filename: "./instance_conf-ins01.yml"
      content: |
        # this file is maintaind by terraform
        ---
        instance: ins01
        address: ${aws_eip.eip-ins01.public_ip}
        user: ec2-user
        port: 22
        identity_file: ~/.ssh/id_rsa
output:
  ssh_login-ins01:
    value: ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa
      ec2-user@${aws_eip.eip-ins01.public_ip}

使用本地文件将接口连接方式与分子对接到实例上。

通过使用Terraform进行部署和销毁确认

准备好tf.json文件。

如果一开始就使用.tf.json或者.tf文件的话就不需要了。
通过yq实用工具,将YAML文件转换为terraform用的.tf.json文件。

$ cd molecule/default/
$ yq . common.tf.yml > common.tf.json
$ yq . ins01.tf.yml > ins01.tf.json

yq在MAC上預設並未安裝。
可以使用Python等其他替代方式實現。

$ cd molecule/default/
$ cat common.tf.yml | python -c "import yaml; import json; import sys; print(json.dumps(yaml.load(sys.stdin, Loader=yaml.FullLoader), indent=2))" > common.tf.json
$ cat ins01.tf.yml | python -c "import yaml; import json; import sys; print(json.dumps(yaml.load(sys.stdin, Loader=yaml.FullLoader), indent=2))" > ins01.tf.json

进行terraform的初始设置 (进行terraform init)

$ terraform init 

Initializing the backend...

Initializing provider plugins...
- Using previously-installed hashicorp/aws v3.13.0
- Using previously-installed hashicorp/local v1.4.0
...
Terraform has been successfully initialized!
...

提供程序文件将放置在.terraform/plugins目录下。
如果事先将其放置在~/.terraform.d/plugins目录中,.terraform/plugins目录将成为一个符号链接。

创建AWS资源(使用terraform apply)

$ terraform apply --auto-approve
...

Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

Outputs:

ssh_login-ins01 = ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ec2-user@52.68.125.189

登入EC2实例,登出。

$ ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ec2-user@52.68.125.189
Warning: Permanently added '52.68.125.189' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
25 package(s) needed for security, out of 39 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-0-1-74 ~]$ ^D
ログアウト
Connection to 52.68.125.189 closed.

AWS资源删除 (Terraform销毁)

$ terraform destroy --auto-approve
...
aws_vpc.vpc01: Destruction complete after 1s

Destroy complete! Resources: 14 destroyed.

CH2. 与分子的合作

分子設定檔案(molecule.yml)

在中文中有多种选择来重新表达上述句子,以下是一种可能的方式:

通过命令「$ molecule init role ROLENAME –driver-name delegated」创建的molecule.yml文件需要进行编辑,在编辑时需与terraform配置文件中的platforms的name保持一致。

---
dependency:
  name: galaxy
driver:
  name: delegated
platforms: # terraformにて作成するec2インスタンスと名前が合致する必要がある
  - name: ins01
provisioner:
  name: ansible
verifier:
  name: ansible

创建用于测试环境的Molecule playbook(create.yml)。

在创建.yml文件时,通过molecule init role ROLENAME –driver-name delegated进行编辑。 Dump任务没有进行任何更改。

---
- name: Create
  hosts: localhost
  connection: local
  gather_facts: false
  #no_log: "{{ molecule_no_log }}"
  tasks:

  # molecule.ymlとterraform設定の整合性検証用1(オプション)
  - name: set_fact molecule_platforms
    set_fact:
      molecule_platforms: "{{ molecule_yml.platforms|map(attribute='name')|list }}"
  - debug:
      var: molecule_platforms

  # インスタンス作成処理本体(必須)
  - name: terraform apply -auto-approve
    shell: |-
      set -ex
      exec 2>&1
      which yq
      which terraform
      ! ls *.tf.json || rm *.tf.json
      ! ls instance_conf-*.yml || rm instance_conf-*.yml
      ls *.tf.yml | awk '{TO=$1; sub(/.tf.yml$/,".tf.json",TO); print "yq . "$1" > "TO}' | bash -x
      terraform init -no-color
      terraform apply -auto-approve -no-color || terraform apply -auto-approve -no-color
    register: r
  - debug:
      var: r.stdout_lines

  # Moleculeに連携する各インスタンスへの接続情報をlistにまとめる(必須)
  - name: Make instance_conf from terraform localfile
    vars:
      instance_conf: []
    with_fileglob:
    - instance_conf-*.yml
    set_fact:
      instance_conf: "{{ instance_conf + [lookup('file',item)|from_yaml] }}"

  # molecule.ymlとterraform設定の整合性検証用2(オプション)
  - name: set_fact terraform_platforms
    set_fact:
      terraform_platforms: "{{ instance_conf|map(attribute='instance')|list }}"
  - debug:
      var: terraform_platforms
  - name: Check molecule_platforms is included in terraform_platforms
    assert:
      that:
      - "{{ (molecule_platforms|difference(terraform_platforms)) == [] }}"

  # Moleculeに連携する各インスタンスへの接続情報をファイル出力(必須)
  - name: Dump instance config
    copy:
      content: |
        # Molecule managed

        {{ instance_conf | to_json | from_json | to_yaml }}
      dest: "{{ molecule_instance_config }}"

分子使用测试环境准备playbook (prepare.yml)。

如果目标系统没有安装Python,则可以使用raw模块来进行Python的安装。
在这里,等待连接的完成。
(由于在terraform结束时实例已经启动,所以几乎不需要等待。)

---
- name: Prepare
  hosts: all
  gather_facts: no
  tasks:
  - wait_for_connection:

使用分子(Molecule)进行测试环境删除的播放书(destroy.yml)。

使用molecule init role ROLENAME命令,并选择–driver-name delegated选项来创建一个名为destroy.yml的文件后,进行编辑。不需要对Dump任务进行更改。

---
- name: Destroy
  hosts: localhost
  connection: local
  gather_facts: false
  no_log: "{{ molecule_no_log }}"
  tasks:

  - name: Populate instance config
    shell: |-
      set -ex
      exec 2>&1
      terraform destroy --auto-approve -no-color || terraform destroy --auto-approve -no-color
    register: r
  - debug:
      var: r.stdout_lines

  - name: Populate instance config
    set_fact:
      instance_conf: {}

  - name: Dump instance config
    copy:
      content: |
        # Molecule managed

        {{ instance_conf | to_json | from_json | to_yaml }}
      dest: "{{ molecule_instance_config }}"

调用测试目标角色的playbook(converge.yml)。

我正在对这个角色的目录名称进行修正,以便能够跟随其更改。

---
- name: Converge
  hosts: all
  tasks:
    - name: "Include {{ playbook_dir|dirname|dirname|basename }}"
      include_role:
        name: "{{ playbook_dir|dirname|dirname|basename }}"

测试目标角色的tasks/main.yml

在这里,获取并显示目标实例的操作系统类型信息

---
- debug:
    var: ansible_distribution

分子测试的实例。

在分子测试中,尝试以连续的方式执行实例创建、反映和删除。

$ molecule test 
--> Test matrix

└── default
    ├── dependency
    ├── lint
    ├── cleanup
    ├── destroy
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    ├── cleanup
    └── destroy

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'lint'
--> Lint is disabled.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Populate instance config] ************************************************
    changed: [localhost]

    TASK [debug] *******************************************************************
    ok: [localhost]

    TASK [Populate instance config] ************************************************
    ok: [localhost]

    TASK [Dump instance config] ****************************************************
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'syntax'

    playbook: /Users/aa220269/repo/repo-test/cookbooks/molecule-delegated-terraform-ec2/molecule/default/converge.yml
--> Scenario: 'default'
--> Action: 'create'

    PLAY [Create] ******************************************************************

    TASK [set_fact molecule_platforms] *********************************************
    ok: [localhost]

    TASK [debug] *******************************************************************
    ok: [localhost] => {
        "molecule_platforms": [
            "ins01"
        ]
    }

    TASK [terraform apply -auto-approve] *******************************************
    changed: [localhost]

    TASK [debug] *******************************************************************
    ok: [localhost] => {
        "r.stdout_lines": [
            "+ which yq",
            "/Users/aa220269/.pyenv/shims/yq",
            "+ which terraform",
            "/usr/local/bin/terraform",
            "+ ls common.tf.json ins01.tf.json",
            "common.tf.json",
            "ins01.tf.json",
            "+ rm common.tf.json ins01.tf.json",
            "+ ls 'instance_conf-*.yml'",
            "ls: instance_conf-*.yml: No such file or directory",
            "+ ls common.tf.yml ins01.tf.yml",
            "+ awk '{TO=$1; sub(/.tf.yml$/,\".tf.json\",TO); print \"yq . \"$1\" > \"TO}'",
            "+ bash -x",
            "+ yq . common.tf.yml",
            "+ yq . ins01.tf.yml",
            "+ terraform init -no-color",
            "",
            "Initializing the backend...",
            "",
            "Initializing provider plugins...",
            "- Using previously-installed hashicorp/aws v3.13.0",
            "- Using previously-installed hashicorp/local v1.4.0",
            "",
            "The following providers do not have any version constraints in configuration,",
            "so the latest version was installed.",
            "",
            "To prevent automatic upgrades to new major versions that may contain breaking",
            "changes, we recommend adding version constraints in a required_providers block",
            "in your configuration, with the constraint strings suggested below.",
            "",
            "* hashicorp/local: version = \"~> 1.4.0\"",
            "",
            "Terraform has been successfully initialized!",
            "",
            "You may now begin working with Terraform. Try running \"terraform plan\" to see",
            "any changes that are required for your infrastructure. All Terraform commands",
            "should now work.",
            "",
            "If you ever set or change modules or backend configuration for Terraform,",
            "rerun this command to reinitialize your working directory. If you forget, other",
            "commands will detect it and remind you to do so if necessary.",
            "+ terraform apply -auto-approve -no-color",
            "data.aws_ami.ami01: Refreshing state...",
            "aws_vpc.vpc01: Creating...",
            "aws_key_pair.key_pair01: Creating...",
            "aws_key_pair.key_pair01: Creation complete after 1s [id=key_pair01]",
            "aws_vpc.vpc01: Creation complete after 3s [id=vpc-004bac783f79ba162]",
            "aws_route_table.route_table01: Creating...",
            "aws_internet_gateway.internet_gateway01: Creating...",
            "aws_security_group.security_group01: Creating...",
            "aws_subnet.subnet01: Creating...",
            "aws_route_table.route_table01: Creation complete after 0s [id=rtb-01a167c14ee021fe3]",
            "aws_subnet.subnet01: Creation complete after 1s [id=subnet-0ffa370a28910b65a]",
            "aws_route_table_association.route_table_association01: Creating...",
            "aws_internet_gateway.internet_gateway01: Creation complete after 1s [id=igw-0cfeaa28a6412a523]",
            "aws_route.route01: Creating...",
            "aws_route_table_association.route_table_association01: Creation complete after 0s [id=rtbassoc-0695a2e2b04fb4728]",
            "aws_security_group.security_group01: Creation complete after 1s [id=sg-05db55c4377f19c4f]",
            "aws_security_group_rule.out_all: Creating...",
            "aws_security_group_rule.in_icmp: Creating...",
            "aws_security_group_rule.in_ssh: Creating...",
            "aws_instance.ins01: Creating...",
            "aws_route.route01: Creation complete after 1s [id=r-rtb-01a167c14ee021fe31080289494]",
            "aws_security_group_rule.out_all: Creation complete after 1s [id=sgrule-3399627109]",
            "aws_security_group_rule.in_ssh: Creation complete after 2s [id=sgrule-740776232]",
            "aws_security_group_rule.in_icmp: Creation complete after 2s [id=sgrule-1963657664]",
            "aws_instance.ins01: Still creating... [10s elapsed]",
            "aws_instance.ins01: Still creating... [20s elapsed]",
            "aws_instance.ins01: Still creating... [30s elapsed]",
            "aws_instance.ins01: Creation complete after 33s [id=i-0f6d44308394d6b5c]",
            "aws_eip.eip-ins01: Creating...",
            "aws_eip.eip-ins01: Creation complete after 1s [id=eipalloc-0c4b8efe50c27636c]",
            "local_file.instance_conf-ins01: Creating...",
            "local_file.instance_conf-ins01: Creation complete after 0s [id=7d42af9527d38119349dc1f1c15e81e7a9c7e02e]",
            "",
            "Apply complete! Resources: 14 added, 0 changed, 0 destroyed.",
            "",
            "Outputs:",
            "",
            "ssh_login-ins01 = ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa ec2-user@54.249.181.45"
        ]
    }

    TASK [Make instance_conf from terraform localfile] *****************************
    ok: [localhost] => (item=/Users/aa220269/repo/repo-test/cookbooks/molecule-delegated-terraform-ec2/molecule/default/instance_conf-ins01.yml)

    TASK [set_fact terraform_platforms] ********************************************
    ok: [localhost]

    TASK [debug] *******************************************************************
    ok: [localhost] => {
        "terraform_platforms": [
            "ins01"
        ]
    }

    TASK [Check molecule_platforms is included in terraform_platforms] *************
    ok: [localhost] => {
        "changed": false,
        "msg": "All assertions passed"
    }

    TASK [Dump instance config] ****************************************************
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=9    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'prepare'

    PLAY [Prepare] *****************************************************************

    TASK [wait_for_connection] *****************************************************
    ok: [ins01]

    PLAY RECAP *********************************************************************
    ins01                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'converge'

    PLAY [Converge] ****************************************************************

    TASK [Gathering Facts] *********************************************************
[WARNING]: Platform linux on host ins01 is using the discovered Python
interpreter at /usr/bin/python, but future installation of another Python
interpreter could change the meaning of that path. See https://docs.ansible.com
/ansible/2.10/reference_appendices/interpreter_discovery.html for more
information.
    ok: [ins01]

    TASK [Include molecule-delegated-terraform-ec2] ********************************

    TASK [molecule-delegated-terraform-ec2 : debug] ********************************
    ok: [ins01] => {
        "ansible_distribution": "Amazon"
    }

    PLAY RECAP *********************************************************************
    ins01                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Running Ansible Verifier

    PLAY [Verify] ******************************************************************

    TASK [Example assertion] *******************************************************
    ok: [ins01] => {
        "changed": false,
        "msg": "All assertions passed"
    }

    PLAY RECAP *********************************************************************
    ins01                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Populate instance config] ************************************************
    changed: [localhost]

    TASK [debug] *******************************************************************
    ok: [localhost]

    TASK [Populate instance config] ************************************************
    ok: [localhost]

    TASK [Dump instance config] ****************************************************
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

--> Pruning extra files from scenario ephemeral directory

请提供一些中文的句子,我会为您进行改写。

使用Terraform构建VPC和EC2实例,并进行ssh连接。

广告
将在 10 秒后关闭
bannerAds