(Karabinerが原因でした…) – Qiita

WordPressのREST APIを使ったSPAを作りたくて、TerraformでまずはAWS環境整えた話 はじめに こちらは Amazon Web Services Advent Calendar 2016 の23日目の記事です。 概要 タイトル通りですが、今回はAWSのアドベントカレンダーということで 「Terraformを使ったAWS環境構築」をターゲットとしたお話です。 続きを読む

[AWS][Terraform] Terraform で Amazon Inspector を導入する

TerraformAmazon Inspector を導入して、CloudWatch Events で定期実行させるための手順。
Terraform は v0.11.2 を使っています。

Inspector の導入

Inspector を導入するには、Assessment targets (評価ターゲット) と Assessment templates (評価テンプレート) を設定する必要があります。

Assessment targets の設定

Terraform で Assessment targets を設定するには、aws_inspector_resource_group, aws_inspector_assessment_target リソースを使用します。
こんな感じです。

inspector_target.tf
variable "project" { default = "my-big-project" }
variable "stage"   { default = "production" }

resource "aws_inspector_resource_group" "inspector" {
    tags {
        project   = "${var.project}"
        stage     = "${var.stage}"
        inspector = "true"
    }
}

resource "aws_inspector_assessment_target" "inspector" {
    name               = "my-inspector-target"
    resource_group_arn = "${aws_inspector_resource_group.inspector.arn}"
}

aws_inspector_resource_group では、対象となるインスタンスを特定するための条件を記述します。
上記の例だと、以下のタグが設定されているインスタンスを対象にします。

Name Value
project my-big-project
stage production
inspector true

aws_inspector_assessment_target では、aws_inspector_resource_group で定義した条件を元に Assessment targets を作成します。

Assessment templates の設定

Terraform で Assessment templates を設定するには、aws_inspector_assessment_template リソースを使用します。
こんな感じです。

inspector_template.tf
variable "inspector-rule" = {
    type = "list"
    default = [
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-bBUQnxMq",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-gHP9oWNT",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-knGBhqEu"
    ]
}

resource "aws_inspector_assessment_template" "inspector" {
    name       = "my-inspector-template"
    target_arn = "${aws_inspector_assessment_target.inspector.arn}"
    duration   = 3600

    rules_package_arns = [ "${var.inspector-rule}" ]
}

output "assessment_template_arn" {
    value = "${aws_inspector_assessment_template.inspector.arn}"
}

rules_package_arns では、利用可能な Inspector rule package の ARN を設定します。
variable にしておくと、後で rule package を変更したい時に楽ですね。
こんな感じで、使用する rule package を変更できます。

terraform.tfvars
"inspector-rule" = [
    "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
    "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-bBUQnxMq"
]

使用できる rule package は、aws-cli で取得してください。

# パッケージ一覧の表示
$ aws --region ap-northeast-1 inspector list-rules-packages
{
    "rulesPackageArns": [
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-bBUQnxMq",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-gHP9oWNT",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-knGBhqEu"
    ]
}
# 詳細を確認
$ aws --region ap-northeast-1 inspector describe-rules-packages 
  --rules-package-arns "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu"
{
    "rulesPackages": [
        {
            "description": "The CIS Security Benchmarks program provides well-defined, un-biased and consensus-based industry best practicesto help organizations assess and improve their security.nnThe rules in this package help establish a secure configuration posture for the following operating systems:nn  -   Amazon Linux version 2015.03 (CIS benchmark v1.1.0)n  n    ",
            "version": "1.0",
            "name": "CIS Operating System Security Configuration Benchmarks",
            "arn": "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
            "provider": "Amazon Web Services, Inc."
        }
    ],
    "failedItems": {}
}

参考URL: Terraform v0.8.5でAWS Inspectorに対応します

これで terraform apply すれば Assessment targets, templates が作成されます。

動作確認

実際に Inspector が実施されるか確認して見ましょう。

$ aws inspector start-assessment-run 
  --assessment-template-arn arn:aws:inspector:ap-northeast-1:************:target/0-xxxxxxxx/template/0-xxxxxxxx
{
    "assessmentRunArn": "arn:aws:inspector:ap-northeast-1:************:target/0-xxxxxxxx/template/0-xxxxxxxx/run/0-7WNjqgGu"
}

実行状況の確認は aws inspector describe-assessment-runs

$ aws inspector describe-assessment-runs 
  --assessment-run-arns arn:aws:inspector:ap-northeast-1:************:target/0-QOvPswHA/template/0-uCIUy636/run/0-n9nnWOem

CloudWatch Events Schedule による定期実行

当初は CloudWatch Events で定期実行するには Lambda から呼び出すようにしなければいけませんでした。
しかし、CloudWatch Event から直接 Inspector を実行できるようになったため、Lambda を使用しなくても aws_cloudwatch_event_targetaws_cloudwatch_event_rule だけで定期実行設定が可能です。

CloudWatch Events で使用する IAM ロールの作成

まずは、CloudWatch Events で使用する IAM ロールを作ります。

cloudwatch-events-iam-role.tf
esource "aws_iam_role" "run_inspector_role" {
    name               = "cloudwatch-events-run-inspector-role"
    assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_policy" "run_inspector_policy" {
    name        = "cloudwatch-events-run-inspector-policy"
    description = ""
    policy      = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "inspector:StartAssessmentRun"
            ],
            "Resource": "*"
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "run_inspector_role" {
    role       = "${aws_iam_role.run_inspector_role.name}"
    policy_arn = "${aws_iam_policy.run_inspector_policy.arn}"
}

CloudWatch Events への登録

CloudWatch Events に登録するために aws_cloudwatch_event_target リソースと aws_cloudwatch_event_rule を作りましょう。

cloudwatch-events.tf
variable "schedule"    { default = "cron(00 19 ? * Sun *)" }

resource "aws_cloudwatch_event_target" "inspector" {
  target_id = "inspector"
  rule      = "${aws_cloudwatch_event_rule.inspector.name}"
  arn       = "${aws_inspector_assessment_template.inspector.arn}"
  role_arn  = "${aws_iam_role.run_inspector_role.arn}"
}

resource "aws_cloudwatch_event_rule" "inspector" {
  name        = "run-inspector-event-rule"
  description = "Run Inspector"
  schedule_expression = "${var.schedule}"
}

schedule_expression の cron() は UTC で設定する必要があるので注意してください。
記述方法は、以下を参考に
参考URL: Rate または Cron を使用したスケジュール式

EC2 への IAM Role の設定と、ユーザーデータによる Inspector エージェントのインストール

評価ターゲットとなる EC2 には、Inspector エージェントがインストールされていて、適切なインスタンスロールが設定されている必要があります。
導入するには、こんな感じ

インスタンスロール

inspectora エージェントを使用するために必要なポリシーをアタッチしたインスタンスロールの作成はこんな感じです。

ec2-instance-role.tf
resource "aws_iam_role" "instance_role" {
    name               = "my-ec2-role"
    path               = "/"
    assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_instance_profile" "instance_role" {
    name = "my-ec2-role"
    role = "${aws_iam_role.instance_role.name}"
}

resource "aws_iam_policy" "inspector" {
    name        = "my-ec2-iam-policy-inspector"
    description = ""
    policy      = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeAvailabilityZones",
                "ec2:DescribeCustomerGateways",
                "ec2:DescribeInstances",
                "ec2:DescribeTags",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeNatGateways",
                "ec2:DescribeNetworkAcls",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribePrefixLists",
                "ec2:DescribeRegions",
                "ec2:DescribeRouteTables",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcEndpoints",
                "ec2:DescribeVpcPeeringConnections",
                "ec2:DescribeVpcs",
                "ec2:DescribeVpn",
                "ec2:DescribeVpnGateways",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeLoadBalancerAttributes",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:DescribeTags",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetHealth"
            ],
            "Resource": "*"
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "inspector" {
    role       = "${aws_iam_role.instance_role.name}"
    policy_arn = "${aws_iam_policy.inspector.arn}"
}

ユーザーデータによる inspector エージェントのインストール

OS は Amazon Linux を想定してます。
ユーザーデータに書いておけば、インスタンス起動直後に inspector エージェントインストールできますね。
参考URL: Amazon Inspector エージェントをインストールする

ssh-key.pemssh-key.pem.pubssh-keygen で適当に作っておきましょう。

ec2.tf
## AMI
##
data "aws_ami" "amazonlinux" {
    most_recent = true
    owners      = ["amazon"]

    filter {
        name   = "architecture"
        values = ["x86_64"]
    }

    filter {
        name   = "root-device-type"
        values = ["ebs"]
    }

    filter {
        name   = "name"
        values = ["amzn-ami-hvm-*"]
    }

    filter {
        name   = "virtualization-type"
        values = ["hvm"]
    }

    filter {
        name   = "block-device-mapping.volume-type"
        values = ["gp2"]
    }
}

## SSH Key Pair
##
resource "aws_key_pair" "deployer" {
    key_name   = "ssh-key-name"
    public_key = "${file(ssh-key.pem.pub)}"
}

## EC2
##
resource "aws_instance" "ec2" {
    ami                         = "${data.aws_ami.amazonlinux.id}"
    instance_type               = "t2.micro"
    key_name                    = "${aws_key_pair.deployer.key_name}"
    iam_instance_profile        = "${aws_iam_instance_profile.instance_role.name}"

    user_data                   = <<USERDATA
#!/bin/bash
# install inspector agent
cd /tmp
/usr/bin/curl -O https://d1wk0tztpsntt1.cloudfront.net/linux/latest/install
/bin/bash install -u false
/bin/rm -f install
USERDATA

    tags {
        project   = "${var.project}"
        stage     = "${var.stage}"
        inspector = "true"
    }
}

現場からは以上です。

続きを読む

mapを使った読みやすいterraform variablesの書き方

スクリーンショット 2018-01-19 0.51.28.png

ディレクトリ構成

以下のように、terraform/provider/aws/env/stgとしました。
変数ファイルであるvariables.tfもメイン処理をするec2.tfも同じディレクトリに置きます。

環境ごとに完全にファイルを分断するイメージで、tfstateファイルも環境ごとに別にします。(保管場所はS3です)
なお、module化はしません。

 %tree 
.
└── provider
    └── aws
        └── env
            └── stg
                ├── backend.tf
                ├── ec2.tf
                └── variables.tf

変数の書き方

Before

これまでterraformの変数ファイルはこんな感じで書いてました。

before_variables.tf
variable "ami" {
  default = "ami-4af5022c"
}

variable "instance_type" {
  default = "t2.micro"
}

variable "instance_key" {
  default = "id_rsa"
}

実際のEC2のパラメータはこれだけではありません。もっとたくさんある上に、AWSのリソースは他にもあるとなるとかなり長い変数ファイルとなります。

特徴としては、variableが毎回並んでしまって行数が増える。読みにくい見にくい探しづらい。

これを解決するのにmapを使いました。

A map value is a lookup table from string keys to string values. This is useful for selecting a value based on some other provided value.
A common use of maps is to create a table of machine images per region, as follows:

sample.tf
variable "images" {
  type    = "map"
  default = {
    "us-east-1" = "image-1234"
    "us-west-2" = "image-4567"
  }
}

After

一つのvariableに対してリソースの値をまとめます。
map型の宣言は省略可能みたいです。

after_variables.tf
variable "ec2_config" {
  type = "map" #省略化
  default = {
    ami = "ami-4af5022c" 
    instance_type = "t2.micro" 
    instance_key = "id_rsa" 
  }
}

こうすることで、リソースごとに値がまとまるので読みやすくなりました。
ではメイン処理をするec2.tfを見ていきましょう。

map型でのec2.tfの書き方

Before

これまでのリソース定義の書き方はこちらです。

before_ec2.tf
resource "aws_instance" "vtryo-web01" {
  ami              = "${var.ami}"
  instance_type    = "${var.instance_type}"
  instance_key     = "${var.instance_key}"

    tags {
    Name = "vtryo-web01"
  }
}

そしてafter版に対するvariableに対応するリソース定義の仕方です。
map型で変数を格納したので、lookup関数を使って値を参照させます。

beforeのときよりやや長くなったように見えますが、ルールを覚えれば(後述)そこまで複雑ではない上、柔軟性は良くなっています。

after_ec2.tf
resource "aws_instance" "vtryo-web" {
  ami              = "${lookup(var.ec2_config, "ami")}" 
  instance_type    = "${lookup(var.ec2_config, "instance_type")}" 
  key_name         = "${lookup(var.ec2_config, "instance_key")}" 

  tags {
    Name = "vtryo-${format("web%02d", count.index + 1)}" 
  }

注意点

resource定義内のami, instance_type, key_nameはterraform側で名前が決まっています。variables.tf内の変数名は任意ですが、こちらは自由には決められないので注意しましょう。
aws_instance

lookup

上記の書き方は、lookup関数でKeyを直接指定する方法を取っています。
こちらを参考にしています。
Terraformのoutputでmapを利用する方法

たとえば以下であれば
ami = "${lookup(var.ec2_config, "ami")}"

variables.tf内のec2_configamiの値を参照する」という意味になります。

format

最終行の"vtryo-${format("web%02d", count.index + 1)}"についてですが、変数格納後の値はvtryo-web01になります。
ちなみに"web%02d"を、"web%01d"にするとvtryo-web1になってしまうので注意です。

terraform init

さて実行です。

% terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (1.7.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, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 1.7"

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 plan

 % terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_instance.vtryo-web
      id:                           <computed>
      ami:                          "ami-4af5022c"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     "id_rsa"
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "vtryo-web01"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>


Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

変数が格納されました!

ちょっと応用

さきほどのec2.tfでNameには"vtryo-${format("web%02d", count.index + 1)}"を書きました。
この出力結果はvtryo-web01です。
これをstg-vtryo-web01のように、環境を先頭に入れる方法を書いておきます。

variable “env”

variables.tfにvariable "env { }"を書きます。

variables.tf
variable "env" { }

variable "ec2_config" {
  default = {
    ami           = "ami-4af5022c"
    instance_type = "t2.micro"
    instance_key  = "id_rsa"
  }
}

{ }の中にdefaultを入れてもよいです。その場合は以下のように書きます。

variables.tf
variable "env" { 
  default = "text message..."
 }

${var.env}

一方でec2.tfには以下を書きます。

resource "aws_instance" "vtryo-web" {
  ami                      = "${lookup(var.ec2_config, "ami")}"
  instance_type            = "${lookup(var.ec2_config, "instance_type")}"
  key_name                 = "${lookup(var.ec2_config, "instance_key")}"
  tags {
    Name = "${var.env}-vtryo-${format("web%02d", count.index + 1)}"
  }
}

${var.env}をつけることで、terraform planしたときに入力を求められます。
入力した内容が、${var.env}に格納されるという仕組みです。

terraform plan

Enter a valueを求められるので今回はstgにしました。
すると、stgという文字列が格納されます。

% terraform plan
var.env
  Enter a value: stg

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_instance.vtryo-web
      id:                           <computed>
      ami:                          "ami-4af5022c"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     "id_rsa"
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "stg-vtryo-web01"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>


Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

ちなみにterraform plan -var "env=stg"とすることで入力なしで格納することが可能です。

今回の補足というか余談

「terraformのbest-practice、わかりづらくない?」

開発者が推奨している構成だとはわかっていますが、素直にそんな感想がありました。
terraform best-practice
(もちろんQiitaにかかれている記事も読んでいます
Terraform Best Practices in 2017

ファイルの中に何度も宣言をしないといけないようですし、どうにも読みづらい印象があります。
また、「Environmentで環境を分ける」構成になっていますが、公式曰く

Workspaces can be used to manage small differences between development, staging, and production, but they should not be treated as the only isolation mechanism.

とあります。我らがGoogle翻訳を使うとこんなことを言ってます。

ワークスペースは、開発、ステージング、およびプロダクションの小さな違いを管理するために使用できますが、唯一の分離メカニズムとして扱うべきではありません。

初見で解読するには骨がいる内容です。何度もterraformを使い込んだらわかるんでしょうか。
ま、単純に私の技術力が追いついていない可能性の方が高いですが(爆)

とはいえ誰でも彼でもterraformの技術力が高いわけではないので、より読みやすくメンテナンスしやすい書き方をしても良いと思っています。

terraformは一歩間違えるとインフラそのものが削除されることがあるので、それを踏まえて確実な方が良いだろうというきもちです。

今回はQiitaの記事にだいぶ助けられたこともあったので、Qiitaに書くことにしました。

シンプルに書きたい欲

terraformにはmodule化が出来る機能があって、それがやや難易度を上げているように思います。

変数をmodule化できることでメリットもありますが、利用ができなければ意味がない。terraformにはmoduleのレジストリがありますが、仕組みを理解しないとやっぱり使うのは大変です。

学習コストばかりかかるくらいなら、いっそ使わずにシンプルにコードを書いて行こうということでした。

best-practiceに固執しない

重要なのは可読性ととっつきやすさだと思ったので、あえて固執せずにディレクトリを構成し、terraformを見やすく書く方法を考えました。
誰かの参考になれば幸いです。

参考

Terraformのoutputでmapを利用する方法 – Qiita
Terraform Best Practices in 2017 – Qiita
TerraformでのAWS環境構築の設定を分ける – Qiita
terraform
さらに一歩楽しむterraform. moduleでIAM UserとPolicy管理を簡素化しよう
本記事はこちらのクローンです。

続きを読む

terraform_remote_stateで値を取得する際にはoutputの位置が大事

やりたかったこと

VPCとそれに乗るサービスを別々のTerraformで管理、VPCのIDをoutputで出力してそれをterraform_remote_stateで呼び出して利用したかった。 これはできた

workspaceとしてvpc-dev, db-devを用意、db-devをデプロイする際にvpc-devのoutputを利用したかったが、まだworkspaceをまたいでのoutputの利用はうまくできていない…ヘルプミー

とりあえずうまく行った部分だけ共有!

だめな例

├── vpc
│   ├── main.tf
│   ├── output.tf
│   └── variables.tf
└── vpc.tf
output.tf
output "vpc_id" {
  value = "${aws_vpc.vpc.id}"
}

main.tfvariable.tfは省略

vpc.tf
module "vpc" {
  source = "./vpc"
}

上記だとmoduleのvpcのoutputになるが、以下の記事からterraformのremote_stateはルートレベルのものしか許容していないためremote_stateは利用できない

https://www.terraform.io/docs/providers/terraform/d/remote_state.html#root-outputs-only

ルートレベル出力のみがアクセス可能です

tfstateを覗いて見るとルートにはoutputがなく、vpcモジュールにoutputがある

terraform.tfstate
~~~~~~~~~略~~~~~~~~~~~~
    "modules": [
            {
            "path": [
                "root"
            ],
            "outputs": {},
~~~~~~~~~略~~~~~~~~~~~~
            "path": [
                "root",
                "vpc"
            ],
            "outputs": {
                "vpc_id": {
                    "sensitive": false,
                    "type": "string",
                    "value": "vpc-*******"
                }
            },
~~~~~~~~~略~~~~~~~~~~~~

良い例

モジュールを呼び出した所で再度outputしてあげればOK

vpc.tf
module "vpc" {
  source = "./vpc"
}

output "vpc_id" {
  value = "${module.vpc.vpc_id}"
}

tfstateを見てみるとルートに値があることが確認できる

terraform.tfstate
~~~~~~~~~略~~~~~~~~~~~~
    "modules": [
            {
            "path": [
                "root"
            ],
            "outputs": {
                "vpc_id": {
                    "sensitive": false,
                    "type": "string",
                    "value": "vpc-********"
                }
            },
~~~~~~~~~略~~~~~~~~~~~~
            "path": [
                "root",
                "vpc"
            ],
            "outputs": {
                "vpc_id": {
                    "sensitive": false,
                    "type": "string",
                    "value": "vpc-*******"
                }
            },
~~~~~~~~~略~~~~~~~~~~~~

これによってremote_stateからoutputを取得できるようになりました

workspaceをまたいだremote_stateの利用ができるのかがまだわかっていないので知ってる人いたら教えてほしいです:bow:

参考リンク

続きを読む

AWS CLIに複数のAWSアカウントを登録する方法

複数のAWSを利用するプロジェクトに携わっている場合、複数のAWSアカウントをCLIで操作する可能性が出てきます。
Secret Keyは作成時しか見えませんので、毎回AWSにログインしてSecret Keyを作り直すこともナンセンスです。
本記事では、AWS CLIに複数のAWSアカウントを登録し、再度configする手間を省く方法をまとめます。

AWS CLIのインストール

AWSの公式ドキュメントを参考にinstallを行います。
https://docs.aws.amazon.com/ja_jp/streams/latest/dev/kinesis-tutorial-cli-installation.html

初期設定

defaultで利用するアカウントを登録します。

$ aws configure
AWS Access Key ID [None]: ${Your Default ID}
AWS Secret Access Key [None]: ${Your Default Secret Key}
Default region name [None]: ${Region}
Default output format [None]: json

AWS CLIに複数のアカウントを登録する

AWS CLIは–profileオプションを利用することで、複数のアカウントを登録することが可能です

$ aws configure --profile xxxx
AWS Access Key ID [None]: ${Your ID}
AWS Secret Access Key [None]: ${Your Secret Key}
Default region name [None]: ${Region}
Default output format [None]: json

では、どのようにcredentialなどは登録されているのでしょうか。
AWS CLIのconfig、およびcredentialを保存しているfileを確認してみましょう。

~/.aws
$ tree ~/.aws
.aws
├── config
└── credentials

まずはconfigから確認してみましょう。

~/.aws/config
[default]
output = json
region = ap-northeast-1
[profile xxxx]
output = json
region = ${Region}

[profile xxxx]といった項目が追加され、先ほど入力した情報と同様の情報が格納されていることが確認できます。
credentialsの方も確認してみましょう。

~/.aws/credentials
[default]
aws_access_key_id = ${Your Default ID}
aws_secret_access_key = ${Your Default Secret Key}
[xxxx]
aws_access_key_id = ${Your ID}
aws_secret_access_key = ${Your Secret Key}

こちらも先ほど入力した情報と同様の情報が格納されていることが確認できました。

AWS CLIの利用方法

–profileオプションを利用することによって、~/.aws以下に保存されている情報から、–profileの引数と同様の項目からkeyなどの情報を参照して利用します。
以下の例は、s3バケットの一覧をアカウントを変えて表示させています。
アカウントが変わることで、s3のバケットが変更されるのがわかると思います。

s3バケットの一覧が変更されていることを確認
$ aws s3 ls
$ aws s3 ls --profile xxxx

まとめ

今回はAWS CLIに複数のAWSアカウントを登録する方法をまとめました。
最近はAWS CLIだけではなく、Serverless frameworkやterraformを利用した構築方法もありますので、そちらの方も随時まとめていけたらと思います。

続きを読む

AWSをterraformで管理しよう!

terraformに興味があって勉強してみたい方、terraformを使ってクラウドをコード管理したい方向けのハンズオンセミナーです。 terraformを使って、実際にAWS上に環境構築することにより、terraformが理解出来る構成となっています。 基本的に自習形式で、こちらで用意した学習コンテンツを進めて頂く形です。詰まったところや … 続きを読む

TerraformをアップグレードしたらVPC周りで怒られた件

Terraformのバージョンをv0.10.7からv0.11.2にアップグレードしたら、planで以下のようなエラーが発生しました。

% terraform plan
 ------------------------------------------------------------------------

Warning: output "database_subnet_group": must use splat syntax to access aws_db_subnet_group.database attribute "id", because it has "count" set; use aws_db_subnet_group.database.*.id to obtain a list of the attributes across all instances



Warning: output "elasticache_subnet_group": must use splat syntax to access aws_elasticache_subnet_group.elasticache attribute "id", because it has "count" set; use aws_elasticache_subnet_group.elasticache.*.id to obtain a list of the attributes across all instances



Warning: output "igw_id": must use splat syntax to access aws_internet_gateway.mod attribute "id", because it has "count" set; use aws_internet_gateway.mod.*.id to obtain a list of the attributes across all instances



Warning: output "vpc_endpoint_s3_id": must use splat syntax to access aws_vpc_endpoint.s3 attribute "id", because it has "count" set; use aws_vpc_endpoint.s3.*.id to obtain a list of the attributes across all instances



Warning: output "vpc_endpoint_dynamodb_id": must use splat syntax to access aws_vpc_endpoint.dynamodb attribute "id", because it has "count" set; use aws_vpc_endpoint.dynamodb.*.id to obtain a list of the attributes across all instances



Error: Error running plan: 4 error(s) occurred:

* module.vpc.output.vpc_endpoint_dynamodb_id: Resource 'aws_vpc_endpoint.dynamodb' not found for variable 'aws_vpc_endpoint.dynamodb.id'
* module.vpc.output.vpc_endpoint_s3_id: Resource 'aws_vpc_endpoint.s3' not found for variable 'aws_vpc_endpoint.s3.id'
* module.vpc.output.elasticache_subnet_group: Resource 'aws_elasticache_subnet_group.elasticache' not found for variable 'aws_elasticache_subnet_group.elasticache.id'
* module.vpc.output.database_subnet_group: Resource 'aws_db_subnet_group.database' not found for variable 'aws_db_subnet_group.database.id'

でも今回エラーとなった構文は、 .tfstate の中にのみ存在する記述だったりして、しばらく悩んでしまいました(dynamodbとか使ってないしw。

原因はどうやら利用していたVPCのモジュールが [DEPRECATED] になった事でした。

利用していたvpcモジュール : terraform-community-modules/tf_aws_vpc

[DEPRECATED] Use https://github.com/terraform-aws-modules/terraform-aws-vpc

なのでvpcモジュール利用箇所を

 module "vpc" {
-  source = "github.com/terraform-community-modules/tf_aws_vpc"
+  source = "terraform-aws-modules/vpc/aws"

   name = "vega_vpc_${var.env}"

のように修正し terraform plan し直したら上述のWarningとErrorが全部消えました。

めでたしめでたし。

ただ、VPC系モジュールが軒並み~ update in-place または -/+ destroy and then create replacement になってしまったのでaplyする際は注意が必要です。

私の場合、たまたまv0.10からv0.11にアップグレードしたタイミングで発覚しただけですが同様のエラーでお困りの人向けにまとめてみました。

続きを読む

Assume-role先のRoleの権限で(AWSの機能|vagrant|terraform|serverless|apex)を使いたい

背景

AWS CLIがAssumeRoleによる自動クレデンシャル取得とMFAに対応しました! | Developers.IO
awscliは上記で説明されているような記述を用いることにより、assume-role権限のあるroleへswitchしてコマンドを実行することができます。

しかし、この記述法は比較的最近導入されたこと、使用されることがそれほど多くないこと、内部的にSTSを使っているため自前で同様の仕組みを実装することは手間がかかることなどにより、通常 ~/.aws/credentials を見て自動的に認証情報を取得してくれるコマンドでもこのAssumeRole記法に対応してくれていないツールがあります(代表的なものにserverless, apex, terraform, vagrant-awsがあります)。

解決策

そこで、認証情報として環境変数が概ね優先されることを利用してラッパーコマンドを開発した人が結構います。
マイナーな欲求かつ簡単に実装できるため多数のリポジトリがありますが、以下がgithub上で自分が見つけた同様の目的のリポジトリ一覧です。

(※星の数は2018/01/09時点)

上記のリポジトリのうち、上2つは期待通り動くことを確認しました。
下3つはすみませんが動作確認をしていません。

結論

remind101/assume-role が期待通り動作しておりかつ星の数的にも定評があると思うので、機能的に問題なければそれを使えばいいと思います。

続きを読む

Spot Fleetを使ってEC2を1/4の料金で運用する

はじめに

普段EC2でサーバーを運用してるのですが、スポットインスタンスを使うことで約1/4の料金で利用できてます。

スクリーンショット 2018-01-08 13.40.58.png

スポットインスタンスについて

スクリーンショット 2018-01-08 13.11.12.png

  • 基本的にオンデマンドインスタンスに比べてかなり安いがオンデマンドインスタンスより値段が高くなる場合もある
  • 設定した最高価格の値段より高くなった時にデフォルトの動作だとインスタンスが削除される

停止にする設定もあります。

https://aws.amazon.com/jp/about-aws/whats-new/2017/09/amazon-ec2-spot-can-now-stop-and-start-your-spot-instances/

容量が指定料金内で利用不可能になりイベントが中断された場合に、Amazon EC2 スポットで Amazon EBS-backed インスタンスを終了する代わりに、それを停止することが可能になりました。

  • 同じインスタンスタイプでもavailability zoneで値段が違う
  • 上位のインスタンスタイプのほうが安くなる場合もある

最高価格

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-spot-instances-history.html

スポットインスタンスをリクエストするときは、デフォルトの上限料金 (オンデマンド料金) を使用することをお勧めします。

スポットインスタンスのインスタンスの価格がオンデマンドインスタンスの価格より高くなる場合があるので、オンデマンドインスタンスの上限に設定にしておいたほうがいいということだと思われます。

Spot Fleet

Spot Fleetを使うことで、予め最高価格と復数のインスタンスタイプとAvailability Zoneを設定しておくことでその中で一番安いスポットインスタンスをn個用意するということを自動で出来るようになります。

また動かしてるスポットインスタンスのインスタンスタイプの価格が高騰して停止した場合に、設定した他の安い条件のものがあった場合にそちらが新規で立ち上がるようになります。

Terraformを使ったECSで使う場合の設定例

user_data/ecs.sh.tpl
#!/bin/bash

echo ECS_CLUSTER=${ecs_cluster} >> /etc/ecs/ecs.config
aws_default_subnet.tf
resource "aws_default_subnet" "1a" {
  availability_zone = "${data.aws_region.current.name}a"
}

resource "aws_default_subnet" "1c" {
  availability_zone = "${data.aws_region.current.name}c"
}
aws_spot_fleet_request.tf
data "template_file" "aws_instance_app_user_data" {
  template = "${file("user_data/ecs.sh.tpl")}"

  vars {
    ecs_cluster = "app"
  }
}

data "aws_ami" "ecs_optimized" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "name"
    values = ["amzn-ami-*-amazon-ecs-optimized"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"]
  }
}

resource "aws_spot_fleet_request" "app" {
  iam_fleet_role  = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-ec2-spot-fleet-tagging-role"
  spot_price      = "0.1290"
  target_capacity = "2"
  valid_until     = "2019-11-04T20:44:20Z"
  terminate_instances_with_expiration = true

  launch_specification {
    ami = "${data.aws_ami.ecs_optimized.id}"
    instance_type = "t2.large"
    iam_instance_profile = "${aws_iam_instance_profile.app.name}"
    vpc_security_group_ids = ["${aws_security_group.app.id}"]
    user_data       = "${data.template_file.aws_instance_app_user_data.rendered}"
    subnet_id       = "${aws_default_subnet.1a.id}"
    associate_public_ip_address = true

    tags {
      Name = "app"
    }
  }

  launch_specification {
    ami = "${data.aws_ami.ecs_optimized.id}"
    instance_type = "m4.large"
    iam_instance_profile = "${aws_iam_instance_profile.app.name}"
    vpc_security_group_ids = ["${aws_security_group.app.id}"]
    user_data       = "${data.template_file.aws_instance_app_user_data.rendered}"
    subnet_id       = "${aws_default_subnet.1a.id}"
    associate_public_ip_address = true

    tags {
      Name = "app"
    }
  }

  launch_specification {
    ami = "${data.aws_ami.ecs_optimized.id}"
    instance_type = "m4.large"
    iam_instance_profile = "${aws_iam_instance_profile.app.name}"
    vpc_security_group_ids = ["${aws_security_group.app.id}"]
    user_data       = "${data.template_file.aws_instance_app_user_data.rendered}"
    subnet_id       = "${aws_default_subnet.1c.id}"
    associate_public_ip_address = true

    tags {
      Name = "app"
    }
  } 
}

この設定の場合t2.largeのap-northeast-1a、m4.largeのap-northeast-1a, m4.largeのap-northeast-1cの中で一番安いもので、かつその料金が$0.1290以下の場合の時にEC2インスタンスを2つ動いてる状態にするという動作になります。
$0.1290はm4.largeのオンデマンドインスタンスの価格です。

またSpot Fleetの有効期限は2019-11-04T20:44:20Zで有効期限が切れた場合にインスタンスを停止するという設定になります。

最後に

一時的にサーバーが止まっても問題ない用途に使う場合は問題ないですが、ダウンタイムをないように運用するのはオンデマンドインスタンスと併用するなど工夫が必要です。

続きを読む

【Terraform】terraform_remote_stateデータソースの使い方

やってみた. 今回は簡単にアカウントB(AccountID:75XXXXXXXXXX)のAWSアカウントIDをTerraformの状態ファイルから取得し、アカウントA(AccountID:58XXXXXXXXXX)にアカウントBを信頼するIAMロールを作成します。 以下のようなディレクトリ構成としてます。 続きを読む