mito’s blog

IT技術メインの雑記。思い立ったが吉日。

[Terraform] 関数を使って、年月日時分秒をユニークな文字列として扱う

この記事は、カサレアル Advent Calendar 2022 の19日目のエントリです。

はじめに

ユニークな文字列が欲しかったので、年月日時分秒を取得し文字列にしました。
以下の関数を組み合わせます。


TFファイル

現在の時間2022年12月18日11時26分29秒20221218112629に変換します。

  • tfファイル
locals {
  # timestampでUTCを取得する
  now_utc = timestamp()
  # timeaddで9hを足してJSTに変換する
  now_jst = timeadd(local.now_utc, "9h") 
  # formatdateで取得したい形式に変換する
  datetime = formatdate("YYYYMMDDhhmmss", local.now_jst)
}

output "datetime"{
  value = local.datetime
}


実行結果

$ terraform apply

Changes to Outputs:
  + datetime = (known after apply)

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes


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

Outputs:

datetime = "20221218112629"
$ 


備考

24時間表記にするため、formatdate関数ではhhを指定します。


[Ansible] Assume Roleモジュールを使う

この記事は、カサレアル Advent Calendar 2022 の18日目のエントリです。

はじめに

最近、AnsibleのAWSコレクションにamazon.awsとcommunity.awsの2つがあることを知りました。
community.awsのほうにAssume Roleモジュールがあったので、それを試してみます。

なお、AssumeRoleってなに?という方は、記憶に残しやすい記事がありますのでご参照ください。


環境

  • ansible core: 2.13.7
  • community.aws: 5.0.0

community.awsの最新版は、Ansible Core 2.11.0以前のバージョンをサポートしていません。

Ansible version compatibility Tested with the Ansible Core 2.12, and 2.13 releases, and the current development version of Ansible. Ansible Core versions before 2.11.0 are not supported. In particular, Ansible Core 2.10 and Ansible 2.9 are not supported.


AssumeRole する Playbook

sts_assume_roleモジュールの実行前後で、ARNやクレデンシャル情報等を出力します。

---
- hosts: localhost
  gather_facts: no

  tasks:
    - shell:
        cmd: aws sts get-caller-identity
      register: result

    - name: before
      debug:
        msg: "{{ result.stdout }}"

    - name: set assume_role
      sts_assume_role:
        role_arn: "arn:aws:iam::*******:role/********"  #引き継ぎたいRoleを指定
        role_session_name: "Test_Role"
      register:  assumed_role

    - name: after
      debug:
        msg: "{{ assumed_role }}"


Playbook の実行結果

sts_assume_roleモジュールにより、ARNや一時的なクレデンシャル情報等が設定されました(ほぼ伏字ですが)。

$ ansible-playbook assume_role.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ********************************************************************

TASK [shell] ************************************************************************
changed: [localhost]

TASK [before] ***********************************************************************
ok: [localhost] => {
    "msg": {
        "Account": "***********",
        "Arn": "arn:aws:iam::***********:user/***********",
        "UserId": "***********"
    }
}

TASK [set assume_role] **************************************************************
changed: [localhost]

TASK [after] ************************************************************************
ok: [localhost] => {
    "msg": {
        "changed": true,
        "failed": false,
        "sts_creds": {
            "access_key": "***********",
            "expiration": "2022-12-17T17:31:46+00:00",
            "secret_key": "**********************",
            "session_token": "*********************************"
        },
        "sts_user": {
            "arn": "arn:aws:sts::***********:assumed-role/***********/Test_Role",
            "assumed_role_id": "***********:Test_Role"
        }
    }
}

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

$ 


以降のタスクでaws_access_key: "{{ assumed_role.sts_creds.access_key }}"のように指定し、AssumeRole後のクレデンシャル情報等を使用します。

[Ansible] ダイナミックインベントリで起動中のインスタンスのIPアドレスを出力する

この記事は、Ansible Advent Calendar 2022 の9日目のエントリです。

はじめに

ダイナミックインベントリを使う時はデフォルトのDNS名をhostsに指定していたのですが、ほかの指定はできないのかと思い、IPアドレスを対象に実施してみました。


インベントリファイル

ファイル名は、仕様でaws_ec2.ymlか***aws_ec2.ymlとする必要があります。

$ cat aws_ec2.yml
---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1
hostnames:
  # 結果をプライベートIPで出力する。パブリックIPは「ip-address」を指定する
  - private-ip-address
include_filters:
  # リストの中に複数の条件をいれると、andでつながる
  - instance-state-name: running
    tag:Name:
    # ワイルドカードが使える
    - "web_*"
compose:
  ansible_host: private_ip_address     # hostsにプライベートIPをしているので、今回は不要

$ 


実行ログ

インスタンスの状況は以下です。

Name インスタンスの状態 プライベートIPアドレス
web_01 実行中 172.31.36.***
web_02 実行中 172.31.32.***
web_03 停止済み 172.31.41.***
weeeeebb!!! 実行中 172.31.16.***


ansible-inventoryコマンドを実行し、確認します。

$ ansible-inventory -i aws_ec2.yml --graph
@all:
  |--@aws_ec2:
  |  |--172.31.32.***
  |  |--172.31.36.***
  |--@ungrouped:
$ 


起動中のweb_01、web_02のプライベートIPアドレスのみ出力できました。


注意

プライベートIPアドレスインスタンスが停止していても割り当てられているので、aws_ec2.ymlのフィルターにあるinstance-state-name : runningが必要です。

aws_ec2.ymlからinstance-state-name : runningを削除した場合、以下のように停止中のインスタンスも表示されます。

$ cat aws_ec2.yml 
---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1
hostnames:
  - private-ip-address
include_filters:
  - tag:Name:
    - "web_*"
$ 
$ ansible-inventory -i aws_ec2.yml --graph
@all:
  |--@aws_ec2:
  |  |--172.31.32.***
  |  |--172.31.36.***
  |  |--172.31.41.***
  |--@ungrouped:
$ 


参考

フィルターの条件をandにする方法が分からなかったので助かりました!

フィルターなどの条件に指定できるEC2インスタンスのパラメータは以下のようです。

[Ansible x Terraform] local-execでPlaybookを実行してみた


この記事は、Ansible Advent Calendar 2022 の2日目のエントリです。

はじめに

今回はAnsibleのTerraformモジュールを使うのではなく、TerraformでEC2インスタンスを作成し、local-execでPlaybookを実行してパッケージをインストールします。
AnsibleのAmazonEC2のインベントリプラグインは使わず、出来る限りTerraformで実装してみます。

Provisioner: local-exec | Terraform | HashiCorp Developer



結論

Playbookを更新しても一度それを実行するnull_resourceが成功していれば、terraform applyでは差異なしと判断され、null_resource(Playbook)は再実行されません。
また、Terrafromが出力するPlaybookのログがきれいに表示されないので、AnsibleからTerraformを呼び出したほうが便利そうです。


環境

  • terraform: v1.3.4
  • ansible core: v2.13.6
  • aws cli: v1.22.34


用意するファイル

  • main.tf
    • 実装内容
      • EC2インスタンスの作成
      • SSHキーの作成
      • セキュリティグループの作成
      • リソースhttpでホストのパブリックIPを取得する
      • リソースlocal_fileでinventory.iniの作成
      • リソースnull_resourceのlocal-execでPlaybookを実行する
  • main.yml
    • 実装内容
      • gitのインストール
  • ansible.cfg
    • 実装内容
      • host_key_checkingの無効


main.tf
provider "aws" {
    region = "ap-northeast-1"
    access_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

# EC2インスタンスの作成
resource "aws_instance" "mito_ec2" {
    ami           = "ami-072bfb8ae2c884cc4"    # Amazon Linux 2
    instance_type = "t2.micro"
    associate_public_ip_address = "true"
    key_name      = aws_key_pair.deployer.key_name
    vpc_security_group_ids = [aws_security_group.sg_mito.id]
    tags = {
        Name = "mito_ec2"
    }
}

# キーペアの作成
resource "tls_private_key" "rsa_4096" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "deployer" {
  key_name   = "mito_key"
  public_key = tls_private_key.rsa_4096.public_key_openssh
  lifecycle {
    ignore_changes  = [key_name]
  }
}

resource "local_file" "private_key_pem" {
  content  = tls_private_key.rsa_4096.private_key_pem
  filename = "/home/ubuntu/.ssh/mito_key.pem"  # ログインユーザに合わせる
}

# セキュリティグループの作成
resource "aws_security_group" "sg_mito" {
  name        = "sg_mito"
  description = "sg_mito"

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["${chomp(data.http.myip.response_body)}/32"]  # ホストのパブリックIPを許可する
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name      = "sg_mito"
  }
}

# 返り値でホストのパブリックIPを取得する
data "http" "myip" {
  url = "http://ipv4.icanhazip.com"
}

# 対象ノードのプライベートIPが記載されたinventory.iniを作成する
resource "local_file" "inventory" {
  content  = aws_instance.mito_ec2.public_ip
  filename = "./inventory.ini"
  file_permission = "0644"
}

# Playbookの実行
resource "null_resource" "playbook" {
  depends_on = [
    aws_instance.mito_ec2  # インスタンス作成後に実行
  ]

  # 対象ノードの起動を待ち、Playbookを実行する
  provisioner "local-exec" {
    interpreter = ["/usr/bin/bash", "-c"]
    command     = <<-EOT
      chmod 600 ${local_file.private_key_pem.filename}
      aws ec2 wait instance-status-ok --instance-ids ${aws_instance.mito_ec2.id}
      ansible-playbook main.yml -i inventory.ini -u ec2-user --private-key=${local_file.private_key_pem.filename}
    EOT
  }
}


main.yml
---
- hosts: all
  gather_facts: no
  become: yes

  tasks:
    - name: install git
      yum:
        name: git
        state: present


ansible.cfg
[defaults]
host_key_checking = False


実行ログ

99行目からPlaybookのログが表示されていますが、ログの順番が変わっています。

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/null from the dependency lock file
- Reusing previous version of hashicorp/tls from the dependency lock file
- Finding latest version of hashicorp/http...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/null v3.2.1
- Using previously-installed hashicorp/tls v4.0.4
- Installing hashicorp/http v3.2.1...
- Installed hashicorp/http v3.2.1 (signed by HashiCorp)
- Using previously-installed hashicorp/aws v4.43.0
- Using previously-installed hashicorp/local v2.2.3

()
$ 
$ 
$ terraform apply
data.http.myip: Reading...
data.http.myip: Read complete after 0s [id=http://ipv4.icanhazip.com]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.mito_ec2 will be created
  + resource "aws_instance" "mito_ec2" {
      + ami                                  = "ami-072bfb8ae2c884cc4"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)
() 

  # local_file.inventory will be created
  + resource "local_file" "inventory" {
      + content              = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0644"
      + filename             = "./inventory.ini"
      + id                   = (known after apply)
    }

  # local_file.private_key_pem will be created
  + resource "local_file" "private_key_pem" {
      + content              = (sensitive)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "/home/ubuntu/.ssh/mito_key.pem"
      + id                   = (known after apply)
    }

  # null_resource.playbook will be created
  + resource "null_resource" "playbook" {
      + id = (known after apply)
    }

()
Plan: 7 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

tls_private_key.rsa_4096: Creating...
aws_security_group.sg_mito: Creating...
tls_private_key.rsa_4096: Creation complete after 1s [id=6d5549db0XXXXXXXXXXXXXXXXX]
local_file.private_key_pem: Creating...
aws_key_pair.deployer: Creating...
local_file.private_key_pem: Creation complete after 0s [id=9f5e11483XXXXXXXXXXXXXXXXXXXX]
aws_key_pair.deployer: Creation complete after 0s [id=mito_key]
aws_security_group.sg_mito: Creation complete after 3s [id=sg-091fbXXXXXXXXXXXX]
aws_instance.mito_ec2: Creating...
aws_instance.mito_ec2: Still creating... [10s elapsed]
aws_instance.mito_ec2: Still creating... [20s elapsed]
aws_instance.mito_ec2: Creation complete after 21s [id=i-02fb1XXXXXXXXXXXX]
local_file.inventory: Creating...
null_resource.playbook: Creating...
local_file.inventory: Creation complete after 0s [id=fecdbeXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]
null_resource.playbook: Provisioning with 'local-exec'...
null_resource.playbook (local-exec): Executing: ["/usr/bin/bash" "-c" "chmod 600 /home/ubuntu/.ssh/mito_key.pem\naws ec2 wait instance-status-ok --instance-ids i-02fb1149af69d72ce\nansible-playbook main.yml -i inventory.ini -u ec2-user --private-key=/home/ubuntu/.ssh/mito_key.pem\n"]
null_resource.playbook: Still creating... [10s elapsed]
null_resource.playbook: Still creating... [20s elapsed]
null_resource.playbook: Still creating... [30s elapsed]
null_resource.playbook: Still creating... [40s elapsed]
null_resource.playbook: Still creating... [50s elapsed]
null_resource.playbook: Still creating... [1m0s elapsed]
null_resource.playbook: Still creating... [1m10s elapsed]
null_resource.playbook: Still creating... [1m20s elapsed]
null_resource.playbook: Still creating... [1m30s elapsed]
null_resource.playbook: Still creating... [1m40s elapsed]

null_resource.playbook (local-exec): PLAY [all] *********************************************************************

null_resource.playbook (local-exec): TASK [install git] *************************************************************
null_resource.playbook: Still creating... [1m50s elapsed]
null_resource.playbook (local-exec): [WARNING]: Platform linux on host xx.xx.xx.xx is using the discovered Python
null_resource.playbook (local-exec): interpreter at /usr/bin/python3.7, but future installation of another Python
null_resource.playbook (local-exec): interpreter could change the meaning of that path. See
null_resource.playbook (local-exec): https://docs.ansible.com/ansible-
null_resource.playbook (local-exec): core/2.13/reference_appendices/interpreter_discovery.html for more information.
null_resource.playbook (local-exec): changed: [xx.xx.xx.xx]

null_resource.playbook (local-exec): PLAY RECAP *********************************************************************
null_resource.playbook (local-exec): xx.xx.xx.xx  : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

null_resource.playbook: Creation complete after 1m54s [id=6559XXXXXXXXXXXXXX]

Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
$ 

[Ansible x Terraform] TerraformでKeyPairの作成やAmazon Secrets ManagerにKeyPairを登録し、Ansibleで利用する

この記事は、カサレアル Advent Calendar 2022 の2日目のエントリです。

はじめに

TerraformでEC2インスタンスやKerPairなどのリソースを作成し、それをAnsibleで活用します。
以下の流れで試します。

  1. [Terraform] EC2インスタンスSSHキー(KeyPair)などのリソースを作成します
  2. [Terraform] Amazon Secrets ManagerにSSHキーを登録します
  3. [Ansible] Amazon Secrets ManagerからSSHキーをダウンロードします
  4. [Ansible] SSHキーを使って、対象ノードにパッケージをインストールします



Terraformのリソース「Secrets Manager」について

リソースaws_secretsmanager_secretでシークレットの名前を、リソースaws_secretsmanager_secret_versionでユーザ名/パスワードやキー/バリューなどのシークレット情報が登録できます。

Terraform Registry

locals {
  KEY = {
    ssh_key = file("./xxxx.pem")
  }
}

resource "aws_secretsmanager_secret" "asm_secret" {
  name = "シークレット名"
}

resource "aws_secretsmanager_secret_version" "asm_secret_version" {
  secret_id     = aws_secretsmanager_secret.asm_secret.id
  secret_string = jsonencode(local.KEY)
}


Ansibleのlookupプラグインamazon.aws.aws_secret」について

lookupプラグインを使って、Amazon Secrets ManagerからKerPair情報を取得します。

amazon.aws.aws_secret lookup – Look up secrets stored in AWS Secrets Manager — Ansible Documentation

"{{ lookup('amazon.aws.aws_secret', 'シークレット名', region='リージョン', aws_access_key='XXXXXX', aws_secret_key='XXXXXX') }}"


環境

  • ホストOS: Amazon Linux 2
  • 対象ノードOS: Amazon Linux 2(terraformで作成)
  • terraform: v1.3.4
  • ansible core: v2.13.6
  • amazon.aws: v5.1.0
  • Python: v3.10.6
  • python3-boto: v2.49.0-4
  • python3-botocore: v1.23.34


用意するファイル

  • main.tf
    • 実装内容
      • EC2インスタンスを作成する
      • KeyPairを作成する
      • Secrets ManagerへKeyPairを登録する
      • セキュリティグループを作成する
        • ホストからのSSHアクセスを許可する
  • main.yml
    • 実装内容
      • Secrets ManagerからKeyPairを取得する
      • KeyPairを使ってgitをインストールする
  • ansible.cfg
    • 実装内容
      • host_key_checkingの無効


main.tf
provider "aws" {
    region = "ap-northeast-1"
    access_key = "XXXXXX"
    secret_key = "XXXXXX"
}

# EC2インスタンスの作成
resource "aws_instance" "mito_ec2" {
    ami           = "ami-072bfb8ae2c884cc4"    # Amazon Linux 2
    instance_type = "t2.micro"
    associate_public_ip_address = "true"
    key_name      = aws_key_pair.deployer.key_name
    vpc_security_group_ids = [aws_security_group.sg_mito.id]
    tags = {
        Name = "mito_ec2"
    }
}

# キーペアの作成
resource "tls_private_key" "rsa_4096" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "deployer" {
  key_name   = "mito_key"
  public_key = tls_private_key.rsa_4096.public_key_openssh
  lifecycle {
    ignore_changes  = [key_name]
  }
}

# セキュリティグループの作成
resource "aws_security_group" "sg_mito" {
  name        = "sg_mito"
  description = "sg_mito"

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["${chomp(data.http.myip.response_body)}/32"]  # ホストのパブリックIPを許可する
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name      = "sg_mito"
  }
}

# 返り値でホストのパブリックIPを取得する
data "http" "myip" {
  url = "http://ipv4.icanhazip.com"
}

# Secrets ManagerSSHキーを登録する
resource "aws_secretsmanager_secret" "asm_secret" {
  name = "test_asm_secret"
}

resource "aws_secretsmanager_secret_version" "asm_secret_version" {
  secret_id     = aws_secretsmanager_secret.asm_secret.id
  secret_string = jsonencode( {"ssh_key" = tls_private_key.rsa_4096.private_key_pem})
}


main.yml

1Play目でSSHキーをダウンロードし、2Play目でパッケージをインストールします。

---
- hosts: localhost
  gather_facts: no

  tasks:
    - name: create ssh_key
      ansible.builtin.copy:
        content: "{{ key.ssh_key}}"
        dest: ./mito_key.pem
        mode: 0600
      vars:
        key: "{{ lookup('amazon.aws.aws_secret', 'test_asm_secret', region='ap-northeast-1', aws_access_key='XXXXXX', aws_secret_key='XXXXXX') }}"

- hosts: all
  gather_facts: no
  become: yes

  tasks:
    - name: install git
      yum:
        name: git
        state: present


ansible.cfg
[defaults]
host_key_checking = False


実行ログ

まずは、Terraformコマンドでリソースを作成します。

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/tls...
- Finding latest version of hashicorp/http...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/tls v4.0.4...
- Installed hashicorp/tls v4.0.4 (signed by HashiCorp)
- Installing hashicorp/http v3.2.1...
- Installed hashicorp/http v3.2.1 (signed by HashiCorp)
- Installing hashicorp/aws v4.44.0...
- Installed hashicorp/aws v4.44.0 (signed by HashiCorp)
()
$ 
$ 
$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.mito_ec2 will be created
  + resource "aws_instance" "mito_ec2" {
      + ami                                  = "ami-072bfb8ae2c884cc4"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)

()
  # aws_secretsmanager_secret.asm_secret will be created
  + resource "aws_secretsmanager_secret" "asm_secret" {
      + arn                            = (known after apply)
      + force_overwrite_replica_secret = false
      + id                             = (known after apply)
      + name                           = "test_asm_secret"
      + name_prefix                    = (known after apply)
      + policy                         = (known after apply)
      + recovery_window_in_days        = 30
      + rotation_enabled               = (known after apply)
      + rotation_lambda_arn            = (known after apply)
      + tags_all                       = (known after apply)

      + replica {
          + kms_key_id         = (known after apply)
          + last_accessed_date = (known after apply)
          + region             = (known after apply)
          + status             = (known after apply)
          + status_message     = (known after apply)
        }

      + rotation_rules {
          + automatically_after_days = (known after apply)
        }
    }

  # aws_secretsmanager_secret_version.asm_secret_version will be created
  + resource "aws_secretsmanager_secret_version" "asm_secret_version" {
      + arn            = (known after apply)
      + id             = (known after apply)
      + secret_id      = (known after apply)
      + secret_string  = (sensitive value)
      + version_id     = (known after apply)
      + version_stages = (known after apply)
    }

()
Plan: 6 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

tls_private_key.rsa_4096: Creating...
aws_secretsmanager_secret.asm_secret: Creating...
aws_security_group.sg_mito: Creating...
()

Apply complete! Resources: 6 added, 0 changed, 0 destroyed.
$ 


AWSコンソールでシークレットマネージャへのSSHキー登録を確認します。


作成したEC2インスタンスのinventory.iniを作成します。

$ cat inventory.ini 
35.79.224.19


Playbookを実行します。

 $ ansible-playbook main.yml -i inventory.ini -u ec2-user --private-key=./mito_key.pem
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.10 (default, Jun  3 2021, 00:02:01) [GCC 7.3.1 20180712 (Red Hat 7.3.1-13)]. This feature will be removed from 
ansible-core in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

PLAY [localhost] ********************************************************************************

TASK [create ssh_key] ***************************************************************************
changed: [localhost]

PLAY [all] **************************************************************************************

TASK [install git] ******************************************************************************
[WARNING]: Platform linux on host 35.79.224.19 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-
core/2.11/reference_appendices/interpreter_discovery.html for more information.
changed: [35.79.224.19]

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

$ 


無事に対象ノードへ接続し、パッケージもインストールできました。

備考

inventory.iniの作成はダイナミックインベントリを使えば不要になるけど手、 SSHキー名などの固定値は、別途管理する必要があります。

[AWS Backup] バックアップとリストアを試してみた

この記事は、カサレアル Advent Calender 2022 の1日目のエントリです。

はじめに

AWS Backup を知ったので、EC2インスタンスを対象にバックアップとリストアを試してみました。

AWS Backup とは?

AWS Backup はフルマネージド型のバックアップサービスであり、AWS のサービス、クラウド内、およびオンプレミス間で簡単に一元化およびデータ保護を自動化できます。

このサービスを使用すると、1 つの場所でバックアップポリシーを設定し、AWS リソースのアクティビティを監視できます。

AWS Backup とは? - AWS Backup




検証用のEC2インスタンスを用意する

検証で使っていたAnsibleサーバがあったので、それを対象とします。
また、リストア後の比較用にインスタンス情報を取得します。

$ aws ec2 describe-instances --instance-ids i-0cf8XXXXXXXXXXXX >> source.txt
$ cat source.txt 
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                    "AmiLaunchIndex": 0,
                    "ImageId": "ami-03f4XXXXXXXXXXXX",
                    "InstanceId": "i-0cf8XXXXXXXXXXXX",
                    "InstanceType": "t3.medium",
                    "KeyName": "xxxxx",
                    "LaunchTime": "2022-11-30T00:19:37.000Z",
                    "Monitoring": {
                        "State": "disabled"
                    },
略


Ansibleのバージョンを確認します。

$ ansible --version
ansible [core 2.13.6]


EC2インスタンスのバックアップを取得する

コンソールを操作し、バックアップを取得します。
インスタンスIDはAnsibleサーバを指定します。

10分程度で完了しました。


バックアップ情報からリストアする

コンソールを操作し、リストアします。


バックアップを取得したEC2インスタンスはそのままで、新たにEC2インスタンスが作成されます
また、Tag.Nameは空白です。

$ aws ec2 describe-instances --instance-id i-07b2XXXXXXXXXXXX >> restore.txt
$ cat restore.txt 
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                    "AmiLaunchIndex": 0,
                    "ImageId": "ami-0604XXXXXXXXXXXX",
                    "InstanceId": "i-07b2XXXXXXXXXXXX",
                    "InstanceType": "t3.medium",
                    "KeyName": "xxxxx",
                    "LaunchTime": "2022-11-30T05:45:16.000Z",
                    "Monitoring": {
                        "State": "disabled"
                    },
略


5分程度で完了しました。
Ansibleのバージョンは変わらずで、無事リストアできました。

$ ansible --version
ansible [core 2.13.6]


バックアップ元のインスタンスとリストアしたインスタンスを比較する

取得したインスタンス情報を比較した結果です。

差分あり

  • ImageId
  • InstanceId
  • PrivateDnsName
  • PrivateIpAddress
  • PublicDnsName
  • PublicIpAddress
  • VolumeId
  • ClientToken
  • IamInstanceProfile
    • IAMロール無しでリストアしたため、リストアしたインスタンスは無し
  • MacAddress
  • NetworkInterfaceId
  • Tags.Name
  • EnableResourceNameDnsARecord
  • ReservationId
  • RequesterId
  • timestampをパラメータに持つKey
// timestampが差分のキーは除外しています
ubuntu@ip-172-31-31-169:~$ diff source.txt restore.txt
8,9c8,9
<                     "ImageId": "ami-03f4XXXXXXXXXXXX",
<                     "InstanceId": "i-0cf8XXXXXXXXXXXX",
---
>                     "ImageId": "ami-0604XXXXXXXXXXXX",
>                     "InstanceId": "i-07b2XXXXXXXXXXXX",
21,22c21,22
<                     "PrivateDnsName": "ip-172-31-XX-XXX.ap-northeast-1.compute.internal",
<                     "PrivateIpAddress": "172.31.XX.XXX",
---
>                     "PrivateDnsName": "ip-172-31-XXX-XXX.ap-northeast-1.compute.internal",
>                     "PrivateIpAddress": "172.31.XXX.XXX",
24,25c24,25
<                     "PublicDnsName": "ec2-18-178-XXX-XXX.ap-northeast-1.compute.amazonaws.com",
<                     "PublicIpAddress": "18.178.XXX.XXX",
---
>                     "PublicDnsName": "ec2-43-206-XXX-XXX.ap-northeast-1.compute.amazonaws.com",
>                     "PublicIpAddress": "43.206.XXX.XXX",
41c41
<                                 "VolumeId": "vol-0906XXXXXXXXXXX"
---
>                                 "VolumeId": "vol-0e2cXXXXXXXXXXX"
45c45
<                     "ClientToken": "165XXXXX-XXXX-XXXX",
---
>                     "ClientToken": "E65XXXXX-XXXX-XXXX",
49,52d48
<                     "IamInstanceProfile": {
<                         "Arn": "arn:aws:iam::XXXXXXXXX:instance-profile/AmazonSSMRoleForInstancesQuickSetup",
<                         "Id": "AIXXXXXXXXXXXXX"
<                     },
76,77c72,73
<                             "MacAddress": "0e:de:XX:XX:XX:XX",
<                             "NetworkInterfaceId": "eni-07aXXXXXXXXXX",
---
>                             "MacAddress": "0e:3a:XX:XX:XX:XX",
>                             "NetworkInterfaceId": "eni-047XXXXXXXXXX",
109,114d104
<                     "Tags": [
<                         {
<                             "Key": "Name",
<                             "Value": "XXXXXX"
<                         }
<                     ],
142c132
<                         "EnableResourceNameDnsARecord": true,
---
>                         "EnableResourceNameDnsARecord": false,
148c138,139
<             "ReservationId": "r-0acXXXXXXXXXX"
---
>             "RequesterId": "16XXXXXXXXXX",
>             "ReservationId": "r-0310XXXXXXXXXX"
$ 


備考

リストアする際、インスタンスプロファイルのIAMロールにBackupがないと、以下のエラーが表示されます。
IAMロールに「iam:PassRole アクセス許可」を追加するか、IAMロールなしでリストアし、リストア後にIAMロールを付与することで解決します。

AWS Backup を使用して Amazon EC2 インスタンスを復元するときに表示されるエンコードされた認可エラーメッセージのトラブルシューティング

[Ansible 2.13] Terraformモジュールを使う際に気を付けるポイント

はじめに

AnsibleでTerraformモジュールを使った際に、気を付けるポイントを書き留めておきます。
以下にはまりました。

  1. Terraformのoutputの変数名にハイフンを使用していたため、Ansibleで呼び出せずエラーになった。
    • 原因はTerraformでは変数名にハイフンを使えるが、Ansibleでは使えないため。
  2. terraformモジュール実行後、次のタスクが失敗することがあった。
    • 原因はsshサービスが起動する前に次のタスクが走っていたため。
    • remote-execはインスタンスの起動待ちを気にしなくていいが、Playbookでは起動完了までwaitをいれたほうが安全。


解決策

以下で解決しました。

  1. outputの変数名をハイフンからアンダースコアに修正しました。
    • Terraform全体では、ハイフンとアンダースコアが混ざっている状態なのでちょっともやもや。
    • OSSを連携させるときに、こういった仕様も確認しなくては。
  2. shellモジュールを用いて、起動するまで待つステータスチェックのAWSコマンドを使用しました。
    • awsモジュールで実現したかったけど、見つけられませんでした。


環境

  • Ubuntu: 22.04.1
  • ansible core: 2.13.6
  • terraform: 1.3.4
  • community.general: 6.0.0
  • aws cli: 1.22.34


terraformで構築するインスタンス

outputの変数名はアンダースコアに修正済みです。
インスタンスIDはAWSコマンドの引数として使用するため、outputで出力しています。

provider "aws" {
    region = "ap-northeast-1"
    access_key = "XXXXX"
    secret_key = "XXXXX"
}

resource "aws_instance" "mito-ec2" {
    ami           = "ami-03f4fa076d2981b45"
    instance_type = "t2.micro"
    associate_public_ip_address = "true"
    vpc_security_group_ids = ["XXXXX"]
    key_name = "XXXXX"
    tags = {
        Name = "mito-ec2"
    }
}

output "ec2_id"{
    value = aws_instance.mito-ec2.id
}


実行するPlaybook

インスタンスの起動完了待ちは、aws ec2 wait instance-status-okコマンドを使用します。
引数に、outputで出力したインスタンスIDを指定します。

---
- hosts: localhost
  gather_facts: no

  tasks:
  - name: terraform apply
    community.general.terraform:
      project_path: 'terraform/'
      force_init: yes
      state: present
    register: result

  - name: wait instance_status_ok
    shell:
      cmd: "aws ec2 wait instance-status-ok --instance-ids {{ result.outputs.ec2_id.value }}"

  - name: get instance_status
    shell:
      cmd: "aws ec2 describe-instance-status --instance-ids {{ result.outputs.ec2_id.value }}"
    register: result_instance_status

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


実行ログ

Playbookを実行します。
aws ec2 describe-instance-statusの実行結果で、起動完了が確認できます。

$ ansible-playbook terraform_module.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the 
implicit localhost does not match 'all'

PLAY [localhost] **************************************************************

TASK [terraform apply] ********************************************************
changed: [localhost]

TASK [wait instance_status_ok] ************************************************
changed: [localhost]

TASK [get instance_status] ****************************************************
changed: [localhost]

TASK [debug] *****************************************************************
ok: [localhost] => {
    "msg": [
        "{",
        "    "InstanceStatuses": [",
        "        {",
        "            "AvailabilityZone": "ap-northeast-1c",",
        "            "InstanceId": "i-0080415c81f9dd541",",
        "            "InstanceState": {",
        "                "Code": 16,",
        "                "Name": "running"",
        "            },",
        "            "InstanceStatus": {",
        "                "Details": [",
        "                    {",
        "                        "Name": "reachability",",
        "                        "Status": "passed"",
        "                    }",
        "                ],",
        "                "Status": "ok"",
        "            },",
        "            "SystemStatus": {",
        "                "Details": [",
        "                    {",
        "                        "Name": "reachability",",
        "                        "Status": "passed"",
        "                    }",
        "                ],",
        "                "Status": "ok"",
        "            }",
        "        }",
        "    ]",
        "}"
    ]
}

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

$ 


エラーメッセージ

outputの変数にハイフンを使った場合のエラーで、ec2-idとしています。
リソースは作成されますが、waitで失敗します。

$ ansible-playbook terraform_module.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the 
implicit localhost does not match 'all'

PLAY [localhost] ***************************************************************

TASK [terraform apply] *********************************************************
changed: [localhost]

TASK [wait instance_status_ok] *************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined 
variable. The error was: 'dict object' has no attribute 'ec2'\n\nThe error appears to 
be in '/home/ubuntu/test/terraform_module.yml': line 13, column 5, but may\nbe 
elsewhere in the file depending on the exact syntax problem.\n\nThe offending line 
appears to be:\n\n\n  - name: wait instance_status_ok\n    ^ here\n"}

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

$