TerraformでAWS AMIの最新を常に使うようにする

TerraformとAWSに同時入門する
の続きで、

最新のAMIが常に指定されるようなami設定にする

を行えるようにします。

確認環境

$ terraform version
Terraform v0.11.1
+ provider.aws v1.5.0

取得するAMIのスペック

今回は2017/12/10時点でのAmazon Linuxの下記スペックにおける最新AMI ID(ami-da9e2cbc)を取得します。
最新AMI IDはこちらで確認できます: Amazon Linux AMI ID

  • リージョン: アジアパシフィック東京
  • 仮想化タイプ: HVM
  • ルートデバイスタイプ: EBS-Backed
  • アーキテクチャ: x86_64
  • ボリュームタイプ: gp2

:warning: 注意点
AMI IDはリージョン毎に異なるようなので複数リージョンにまたがった環境の場合は注意が必要かもしれません

結論

簡易版

フィルタ条件はAMI Image名の条件のみ

aws_ami.tf
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners = ["amazon"]

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

詳細版

フィルタ条件として今回の指定スペックをすべて指定

参考: Terraformでもいつでも最新AMIからEC2を起動したい

aws_ami.tf
data "aws_ami" "amazon_linux" {
  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"]
  }
}

EC2側の設定

ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "${data.aws_ami.amazon_linux.id}"
  instance_type          = "t2.micro"
}

確認

$ terraform plan

  + aws_instance.web-server
      id:                           <computed>
      ami:                          "ami-da9e2cbc"
      ...

AWS CLIでAmazon Linux AMI image名を取得してみる

準備

AWS Command Line Interface のインストール

簡易版のフィルタ条件の説明

AWS CLIで最新のAmazon LinuxのAMI IDを取得する
上記でのシェルを参考に今回取得したい条件に変更します

get-aws-ec2-image.sh.sh
...

describe_images(){
    echo $timestamp > $timestamp_file
    aws ec2 describe-images 
-       --owners self amazon 
+       --owners amazon 
        --filters 
+         Name=name,Values="amzn-ami-hvm-*" 
          Name=virtualization-type,Values=hvm 
          Name=root-device-type,Values=ebs 
          Name=architecture,Values=x86_64 
-         Name=block-device-mapping.volume-type,Values=standard
+         Name=block-device-mapping.volume-type,Values=gp2
}

...

実行結果

$ zsh ./get-aws-ec2-image.sh

amzn-ami-hvm-2014.03.2.x86_64-gp2: ami-df470ede
amzn-ami-hvm-2014.09.0.x86_64-gp2: ami-45072844
amzn-ami-hvm-2014.09.1.x86_64-gp2: ami-4585b044
amzn-ami-hvm-2014.09.2.x86_64-gp2: ami-1e86981f
amzn-ami-hvm-2015.03.0.x86_64-gp2: ami-cbf90ecb
amzn-ami-hvm-2015.03.1.x86_64-gp2: ami-1c1b9f1c
amzn-ami-hvm-2015.09.0.x86_64-gp2: ami-9a2fb89a
amzn-ami-hvm-2015.09.1.x86_64-gp2: ami-383c1956
amzn-ami-hvm-2015.09.2.x86_64-gp2: ami-59bdb937
amzn-ami-hvm-2016.03.0.x86_64-gp2: ami-f80e0596
amzn-ami-hvm-2016.03.1.x86_64-gp2: ami-29160d47
amzn-ami-hvm-2016.03.2.x86_64-gp2: ami-6154bb00
amzn-ami-hvm-2016.03.3.x86_64-gp2: ami-374db956
amzn-ami-hvm-2016.09.0.20160923-x86_64-gp2: ami-1a15c77b
amzn-ami-hvm-2016.09.0.20161028-x86_64-gp2: ami-0c11b26d
amzn-ami-hvm-2016.09.1.20161221-x86_64-gp2: ami-9f0c67f8
amzn-ami-hvm-2016.09.1.20170119-x86_64-gp2: ami-56d4ad31
amzn-ami-hvm-2017.03.0.20170401-x86_64-gp2: ami-859bbfe2
amzn-ami-hvm-2017.03.0.20170417-x86_64-gp2: ami-923d12f5
amzn-ami-hvm-2017.03.1.20170617-x86_64-gp2: ami-bbf2f9dc
amzn-ami-hvm-2017.03.1.20170623-x86_64-gp2: ami-3bd3c45c
amzn-ami-hvm-2017.03.1.20170812-x86_64-gp2: ami-4af5022c
amzn-ami-hvm-2017.03.rc-0.20170320-x86_64-gp2: ami-be154bd9
amzn-ami-hvm-2017.03.rc-1.20170327-x86_64-gp2: ami-10207a77
amzn-ami-hvm-2017.09.0.20170930-x86_64-gp2: ami-2a69be4c
amzn-ami-hvm-2017.09.1.20171103-x86_64-gp2: ami-2803ac4e
amzn-ami-hvm-2017.09.1.20171120-x86_64-gp2: ami-da9e2cbc
amzn-ami-hvm-2017.09.rc-0.20170913-x86_64-gp2: ami-d424e7b2

最新AMI ID(ami-da9e2cbc)が取得できているのがわかります。

amzn-ami-hvm-2017.09.1.20171120-x86_64-gp2: ami-da9e2cbc

取得結果からわかるようにImage名にはスペック名も含まれていることがわかります。

よって今回のスペックのAMI IDはImage名でのフィルタ条件のみで取ることができるため、簡易版ではImage名の値が"amzn-ami-hvm-*-x86_64-gp2"とするフィルタ条件1つで最新のAMI IDを取得していました。

また、aws_ami.tfではmost_recent = trueという設定により取得した中で最新のAMI IDが取得されます。

:warning: Image名の命名規則が変更された場合に指定スペックの最新が取得できなくなるので注意ください

フィルタ条件をnameのみにしてAWS CLIでも確認

AWS CLI側でも一応確認

get-aws-ec2-image.sh
describe_images(){
    echo $timestamp > $timestamp_file
    aws ec2 describe-images 
        --owners amazon 
        --filters 
          Name=name,Values="amzn-ami-hvm-*-x86_64-gp2" 
-          Name=virtualization-type,Values=hvm 
-          Name=root-device-type,Values=ebs 
-          Name=architecture,Values=x86_64 
-          Name=block-device-mapping.volume-type,Values=gp2
}
$ zsh ./get-aws-ec2-image.sh | grep ami-da9e2cbc

amzn-ami-hvm-2017.09.1.20171120-x86_64-gp2: ami-da9e2cbc

最新AMI ID(ami-da9e2cbc)が取得できているのがわかります。

その他の参考

続きを読む

CloudFormationで、ECSのCI/CD環境を構築した際のハマりどころ 〜CodePipeline,CodeBuild,KMSも添えて〜

Classiアドベントカレンダー4日目です。
本日は、ECSを利用して、AWS上でAWSどっぷりのCI/CD環境を準備したときのお話になります。

今年のre:InventでEKSとFargateがリリースされましたが、東京リージョンに来てなかったり、プレビュー段階だったりで、まだしばらくは参考になる部分はありそうかなと^^;

1.背景

などで、AWS公式でもECS環境下のCloudFormation(以下、CFn)を使ったデプロイ方法が紹介されています。
とはいえ、現実の要件でCFnで実装しようとすると、デフォルト設定だと失敗したり、ドキュメントだけだと、GUIで設定できる部分がCFnでの書き方がわからかったりして、いくつかハマった内容があったので、3種類ぐらいの特徴を抜粋して書いてみようと思います。

2.TL;DR

ECSを使うなら、

  • ALBとECSの動的ポート機能を組み合わせる
  • IAM Role,KMS,SSMパラメータストアを組み合わせる
  • CodePipelineで複数リポジトリからのコード取得を行う

これらの機能を全部CFnでやろうとすると、一部aws-cliなどを使う必要がありますが、
ひとまずDevとOpsでうまく権限を分担したCI/CD環境を構築できるのではないかなと思います。

3.特徴解説

3-1. ALBとECSの動的ポート機能の組み合わせ

qiita_ecs_port.png

EC2へ割り当てるSecurityGroupは、ECSの動的ポート機能を利用するため、インバウンドのTCPポートを開放しておきます。

securitygroup.yml
ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
        VpcId: !Ref VpcId
       GroupName: sample
       GroupDescription: "ALB Serurity Group"
       SecurityGroupIngress:
            -
                CidrIp: 0.0.0.0/0
                IpProtocol: tcp
                FromPort: 443
                ToPort: 443
EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
        VpcId: !Ref VpcId
       GroupName: sample
       GroupDescription: "EC2 Serurity Group"
       SecurityGroupIngress:
            -
                SourceSecurityGroupId: !Ref ALBSecurityGroup
                IpProtocol: tcp
                FromPort: 0
                ToPort: 65535

ECSの動的ポートを有効にするため、PortMappingsの設定でホストのポートを0に設定します。

ecs.yml
ECSTask:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
        Family: sample
        NetworkMode: bridge
        ContainerDefinitions:
            -
                Name: sample
                Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}:${ImageTag}"
                Cpu: 2
                Memory: 128
                PortMappings:
                    -
                        ContainerPort: 80
                        HostPort: 0
                Essential: true
                Ulimits:
                    -
                        Name: nofile
                        SoftLimit: 65535
                        HardLimit: 65535
                Environment:
                    -
                        Name: TZ
                        Value: Asia/Tokyo
                LogConfiguration:
                    LogDriver: awslogs
                    Options:
                        awslogs-group: sample
                        awslogs-region: !Sub ${AWS::Region}
                        awslogs-stream-prefix: !Ref ImageTag
    Service:
        Type: "AWS::ECS::Service"
        Properties:
            ServiceName: sample
            Cluster: !Ref ECSCluster
            DesiredCount: 1
            TaskDefinition: !Ref ECSTask
            Role: !Ref ECSServiceRole
            PlacementStrategies:
                -
                    Type: spread
                    Field: instanceId
            LoadBalancers:
                -
                    ContainerName: sample
                    ContainerPort: 80
                    TargetGroupArn: !Ref ALBTargetGroup

注意点

複数のEC2でECSを運用するのであれば、PlacementStrategiesの設定を行っておかないと、random配置ECSのタスクが一つのホストだけに偏ってしまったりすることがあります。

3-2. DevとOpsで別gitリポジトリを運用しつつ、CodePipelineのデプロイフェーズでCFnのChangeSetを使う

qiita_codepipeline.png

デプロイにCFnを利用することで、デプロイの実行記録の管理やCFnで記載された部分のインフラ部分のテストを行いつつ、デプロイをすることが可能になります。
また、Sourceフェーズで、CFnの内容やEC2のASGやAMI設定の管理を行うOps管轄リポジトリと、Dockerコンテナ化するアプリロジックが含まれているDev管轄リポジトリを分割することで、
運用フェーズに入ったときにDevとOpsで独立して、デプロイを行うことができます。

codepipeline.yml
CodePipeline:
    Type: "AWS::CodePipeline::Pipeline"
    Properties:
        Name: sample
        ArtifactStore:
            Type: S3
            Location: sample
        RoleArn: !Ref BuildRole
        Stages:
            -
                Name: Source
                Actions:
                    -
                        Name: AppSource
                        RunOrder: 1
                        ActionTypeId:
                            Category: Source
                            Owner: ThirdParty
                            Version: 1
                            Provider: GitHub
                        Configuration:
                            Owner: !Ref GithubOwner
                            Repo: !Ref GithubAppRepo
                            Branch: !Ref GithubAppBranch
                            OAuthToken: !Ref GithubToken
                        OutputArtifacts:
                            - Name: AppSource
                    -
                        Name: InfraSource
                        RunOrder: 1
                        ActionTypeId:
                            Category: Source
                            Owner: ThirdParty
                            Version: 1
                            Provider: GitHub
                        Configuration:
                            Owner: !Ref GithubOwner
                            Repo: !Ref GithubInfraRepo
                            Branch: !Ref GithubInfraBranch
                            OAuthToken: !Ref GithubToken
                        OutputArtifacts:
                            - Name: InfraSource
            -
                Name: Build
                Actions:
                    -
                        Name: CodeBuild
                        RunOrder: 1
                        InputArtifacts:
                            - Name: AppSource
                        ActionTypeId:
                            Category: Build
                            Owner: AWS
                            Version: 1
                            Provider: CodeBuild
                        Configuration:
                            ProjectName: !Ref CodeBuild
                        OutputArtifacts:
                            - Name: Build
            -
                Name: CreateChangeSet
                Actions:
                    -
                        Name: CreateChangeSet
                        RunOrder: 1
                        InputArtifacts:
                            - Name: InfraSource
                            - Name: Build
                        ActionTypeId:
                            Category: Deploy
                            Owner: AWS
                            Version: 1
                            Provider: CloudFormation
                        Configuration:
                            ChangeSetName: Deploy
                            ActionMode: CHANGE_SET_REPLACE
                            StackName: !Sub ${AWS::StackName}
                            Capabilities: CAPABILITY_NAMED_IAM
                            TemplatePath: !Sub "Source::sample.yml"
                            ChangeSetName: !Ref CFnChangeSetName
                            RoleArn: !Ref BuildRole
                            ParameterOverrides: !Sub |
                                {
                                    "ImageTag": { "Fn::GetParam" : [ "Build", "build.json", "tag" ] },
                                    "AppName": "${AppName}",
                                    "OwnerName": "${OwnerName}",
                                    "RoleName": "${RoleName}",
                                    "StageName": "${StageName}",
                                    "VpcId": "${VpcId}"
                                }
            -
                Name: Deploy
                Actions:
                    -
                        Name: Deploy
                        ActionTypeId:
                            Category: Deploy
                            Owner: AWS
                            Version: 1
                            Provider: CloudFormation
                        Configuration:
                            ActionMode: CHANGE_SET_EXECUTE
                            ChangeSetName: !Ref CFnChangeSetName
                            RoleArn: !Ref BuildRole
                            StackName: !Sub ${AWS::StackName}

注意点

  • CodePipelineのキックは、PRがマージされたタイミングなので、(一応、CodePipelineにはTestフェーズもあるが)マージ前のテストなどはCircleCIとかに任せた方がよいかも
  • ParameterOverridesで上書きするパラメータは、CFnのParametersに設定している項目に応じて設定する
  • Sourceフェーズで持ってこれるリポジトリは2つまで。コンテナビルドに持ってくるのがもっとある場合、CodeBuild内でこちらの記事のように、githubから引っ張ってきて、ビルドするなどの対応が必要になりそう

3-3. CodeBuildでDockerイメージを作る際、KMSとSSMパラメータストアを利用する

qiita_codebuild.png

このあたりはAWSの恩恵をフルに受けている部分かなと。
RDSのパスワードや秘密鍵など、gitリポジトリ内で管理したくない情報は、SSMパラメータストアを使って、Dockerイメージを作成するときに環境変数を埋め込みます。

codebuild.yml
CodeBuild:
    Type: AWS::CodeBuild::Project
    Properties:
        Name: sample
        Source:
            Type: CODEPIPELINE
        ServiceRole: !Ref BuildRole
        Artifacts:
            Type: CODEPIPELINE
        Environment:
            Type: LINUX_CONTAINER
            ComputeType: BUILD_GENERAL1_SMALL
            Image: "aws/codebuild/docker:1.12.1"
            EnvironmentVariables:
                -
                    Name: AWS_DEFAULT_REGION
                    Value: !Sub ${AWS::Region}
                -
                    Name: AWS_ACCOUNT_ID
                    Value: !Sub ${AWS::AccountId}
                -
                    Name: IMAGE_REPO_NAME
                    Value: !Ref ECRRepoName

docker buildするときに、--build-argに秘匿情報として環境変数を引き渡し、できあがったイメージをECRにpushする。

buildspec.yml
version: 0.2

phases:
    pre_build:
        commands:
            - $(aws ecr get-login --region $AWS_DEFAULT_REGION)
            - IMAGE_TAG="${CODEBUILD_RESOLVED_SOURCE_VERSION}"
            - DB_PASSWORD=$(aws ssm get-parameters --names rds_pass --with-decryption --query "Parameters[0].Value" --output text)
    build:
        commands:
            - docker build --build-arg DB_PASSWORD="${DB_PASSWORD}" -t "${IMAGE_REPO_NAME}:${IMAGE_TAG}" .
            - docker tag "${IMAGE_REPO_NAME}:${IMAGE_TAG}" "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}:${IMAGE_TAG}"
    post_build:
        commands:
            - docker push "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}:${IMAGE_TAG}"
            - printf '{"tag":"%s"}' "${IMAGE_TAG}" > build.json
artifacts:
    files:
        - build.json
    discard-paths: yes
(snip)
ARG DB_PASSWORD
ENV DB_PASSWORD=${DB_PASSWORD}
(snip)

実運用する際は、IAM Roleを使う権限も意識して、KMSのKeyを利用するIAM UserやIAM Roleを設定する。

kms.yml
KMSKey:
    Type: "AWS::KMS::Key"
    Properties:
        Description: sample-key
        KeyPolicy:
            Version: "2012-10-17"
            Id: "key-default-1"
            Statement:
                -
                    Sid: "Allow use of the key"
                    Effect: "Allow"
                    Principal:
                        AWS: !GetAtt BuildRole.Arn
                    Action:
                        - "kms:DescribeKey"
                        - "kms:Decrypt"
                    Resource: "*"

注意点

  • SSMパラメータにおける、SecureString型の値登録
    3-3.でSSMパラメータストアで暗号化する際、SecureString型はCFnに対応していない。
    そのため、aws-cliで設定することにした。TerraformはSecureString型に対応しているので、CFn側でも対応して欲しいところ…
$ aws ssm put-parameter --name rds-pass --value PASSWORD --type SecureString --key-id hogehoge

4. その他の雑多なハマりどころ

4-1. ECSのAMIのデフォルト設定

  • EBSのストレージタイプのデフォルトがHDD
    LaunchConfigurationのBlockDeviceMappingsで、gp2を明示的に指定してあげる。
  • WillReplace用のシグナルを送るcfn-signalが未インストール
    UserDataの中で記載しておく。シグナルを送るタイミングは、どこまでAMIに手を入れるかによって変更する。
LaunchConfig:
    Type: "AWS::AutoScaling::LaunchConfiguration"
    Properties:
        AssociatePublicIpAddress: true
        KeyName: sample
        IamInstanceProfile: sample
        ImageId: ami-e4657283
        SecurityGroups:
            - !Ref SecurityGroup
        InstanceType: t2.micro
        BlockDeviceMappings:
            -
                DeviceName: "/dev/xvda"
                Ebs:
                    VolumeType: gp2
                    VolumeSize: 30
        UserData:
            Fn::Base64: !Sub |
                #!/bin/bash
                echo ECS_CLUSTER=${ECSClusterName} >> /etc/ecs/ecs.config
                sudo yum install -y aws-cfn-bootstrap
                sleep 60
                /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
AutoScalingGroup:
    Type: "AWS::AutoScaling::AutoScalingGroup"
    Properties:
        LaunchConfigurationName: sample
        DesiredCapacity: 2
        MaxSize: 3
        MinSize: 2
        VPCZoneIdentifier:
            - !Ref PublicSubnet1
            - !Ref PublicSubnet2
    CreationPolicy:
        ResourceSignal:
            Count: 1
            Timeout: PT5M
    UpdatePolicy:
        AutoScalingReplacingUpdate:
            WillReplace: true

5.まとめ

もう少しきれいな書き方がありそうだけど、実運用でよくある要件の参考程度になれば幸いです。
EC2のASGまわりの設定は、従来のECSだとこのような形で大分インフラ側を意識しないといけない構成です。今後、re:Inventで発表されたEKSやFargateなどとも比べながら、本環境をアップデートしていければよいなと思います。

続きを読む

TerraformとAWSに同時入門する

GMOペパボ、ムームードメインのエンジニア@litencattです。

昨日は@dp42による、“Hello, World.”の次としてのチャットボットでした。

今日は、入社以来専らWeb開発がメインでやってきたけど、最近興味のあるAWSやTerraformによるインフラ構築について読んだ本をベースにやってみたことについて書いていきます。

やること

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版のインフラ環境や手順を参考に、AWS環境構築をTerraformを用いて行います。

なお今回はApacheなどEC2インスタンスに対する各種インストールは直接インスタンス内に入ってコマンド実行しています。

ゴール

AWS上に以下の環境構築を行ないます

  • EC2インスタンス x 2台

    • Webサーバ

      • インターネットゲートウェイを持つ
      • Apache(httpd)上でWordPressが動作している
    • DBサーバ
      • NATゲートウェイを通してインターネットに接続する
      • MySQLが起動している

具体的には以下のAWSリソースを扱います。

  • EC2
  • VPC
  • サブネット
  • ルートテーブル
  • インターネットゲートウェイ
  • セキュリティグループ
  • Elastic IP
  • NATゲートウェイ

今回参考にした本について

ネットワークにつての基礎的な用語について詳しい説明があったり、AWSで構築していく環境についての図がとてもわかりやすいのでこのあたり初めてな人にはおすすめな本だと思いました!

Terraformとは

  • AWSなど様々なサービスProviderに対して、サーバの構築・変更・管理を行うためのツール
  • HashiCorpのプロダクト
  • Enterprise版もあるみたい

ペパボではプライベートクラウドとしてOpenStackを自社運用しています。
TerraformはOpenStackにも対応しており、最近は各サービスのインフラ管理がTerraformで行われるようになってきています。

今回はこのTerraformをAWSに対して使っていきます。
https://www.terraform.io/docs/providers/aws/index.html

今回のコードのレポジトリ

https://github.com/litencatt/terraform-aws-templates

準備

Terraformのインストール(Mac)

$ brew install terraform

使用バージョン

2017/12/3の執筆時点での最新リリースバージョンをつかいます

$ terraform version
Terraform v0.11.1
+ provider.aws v1.5.0

https://github.com/hashicorp/terraform/blob/master/CHANGELOG.md#0111-november-30-2017

主にVim向け

HCL扱う場合は入れとくと便利そうです
https://github.com/hashivim/vim-hashicorp-tools

AWSの準備

terraform.tfvarsの設定

今回はAWSのアクセスキーなどの秘匿情報をterraform.tfvarsに持つようにしています。
ここに作成したアクセスキーとシークレットアクセスキーを設定してください。
リージョンなども変更したい場合は必要に応じて変更してください。

terraform.tfvars
access_key = "AWS_ACCESS_KEY"
secret_key = "AWS_SECRET_KEY"
region     = "ap-northeast-1"
key_name   = "KEY_PAIR_NAME"

:warning:実際のアクセスキーなどが書かれたterraform.tfvarsはレポジトリには登録しないよう注意ください

EC2インスタンスへのログイン時に必要な鍵ファイルについて

今回はAWSのダッシュボード上のキーペアで鍵を作成し、それをEC2インスタンス作成時に使用するように指定しています。そのため、key_nameには作成したキーペア名を設定してください。

VPC作成

まずはVPCを作成します
https://github.com/litencatt/terraform-aws-templates/pull/1

main.tf
+variable "access_key" {}
+variable "secret_key" {}
+variable "region" {}
+
+provider "aws" {
+  access_key = "${var.access_key}"
+  secret_key = "${var.secret_key}"
+  region     = "${var.region}"
+}
+
+resource "aws_vpc" "vpc-1" {
+  cidr_block = "10.0.0.0/16"
+  tags {
+    Name = "vpc-1"
+  }
+}

main.tfファイル作成後$ terraform initを実行し、AWSのpluginを取得します。

その後、$ terraform applyを実行して成功するとVPCが作成され、AWSのVPCダッシュボードのVPCページでも確認することが出来ます。
image

ちなみにv0.11.0より$ terraform applyした場合は下記のようにyesを入力しないとapplyが実行されないように変更されています。

$ terraform apply

(省略)

Plan: 15 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:

initせずにplanなどを実行した場合のエラー

$ terraform initを先に実行してください

$ terraform plan
Plugin reinitialization required. Please run "terraform init".
Reason: Could not satisfy plugin requirements.
...(略

以降の作業

すべて記事内に書くと結構なボリュームになりそうなので、以降の作業内容については各PRを参照ください。
PRを作成して手順や差分をわかりやすくし、必要に応じて説明を入れています:nerd:

WordPressページの表示確認

NATゲートウェイの作成のPR内の作業までをすべて完了後、
WebサーバのURLに対してブラウザよりアクセスするとWordPressのスタートページの表示を確認することが出来ます。
image

:warning:この記事で作成したインスタンスは既にdestroy済みなのでPR上のURLにはアクセスできませんのでご注意ください

TerraformでAWSの環境構築してみて

今後のやっていき

最後に

今回、WordPress環境をAWS上にTerraformを主に使って構築してみましたが、
こんなWordPress環境だけでなく、Node.jsやRails環境などがなんと約10秒で出来上がってしまうというロリポップ!マネージドクラウドのβ版が現在無料公開中ですので、そちらも是非宜しくお願いします:smile:
https://mc.lolipop.jp/

参考にさせて頂いたサイト

など多数

続きを読む

TerraformでAWS環境の構築する時に良く使う書き方

AWSを使う設定

provider.tf
provider "aws" {}

AWSのregionとaccount idを取得する

data.tf
data "aws_region" "current" {
  current = true
}

data "aws_caller_identity" "current" {}

regionの取得

data.aws_region.current.name

account idの取得

data.aws_caller_identity.current.account_id

アプリ名を変数にしておく

variable.tf
variable "kptboard" {
  default = "kptboard"
}

変数にして、基本的に名前を設定する部分はその変数を使うことでkptboard-stg,kptboard-prodのように書き換えるだけで他の環境を作りやすくなる。

aws_ecs_cluster.tf
resource "aws_ecs_cluster" "kptboard" {
  name = "${var.kptboard}"
}

最新のEC2のAMIを使うようにする

aws_instance.tf
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_instance" "kptboard" {
  ami = "${data.aws_ami.ecs_optimized.id}"
  instance_type = "${var.aws_instance_kptboard_instance_type}"
  iam_instance_profile = "${aws_iam_instance_profile.kptboard_ec2.name}"
  vpc_security_group_ids = ["${aws_security_group.kptboard_ec2.id}"]
  user_data       = "${data.template_file.aws_instance_kptboard_user_data.rendered}"
  key_name        = "${var.aws_instance_kptboard_key_name}"
  subnet_id       = "${aws_subnet.kptboard_public_a.id}"
  associate_public_ip_address = true
  tags {
    Name = "${var.kptboard}"
  }
}

自動的に最新のECS-optimized AMIが取得できる

IAMのポリシーを別ファイルでjsonのテンプレートに切り出す

kptboard_ssm_policy.json.tpl
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameters"
      ],
      "Resource": "arn:aws:ssm:${region}:${account_id}:parameter/${kptboard}.*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": "arn:aws:kms:us-east-1:${account_id}:key/alias/aws/ssm"
    }
  ]
}

aws_iam_policy.tf
data "template_file" "kptboard_ssm_policy" {
  template = "${file("policies/kptboard_ssm_policy.json.tpl")}"

  vars {
    account_id = "${data.aws_caller_identity.current.account_id}"
    region = "${data.aws_region.current.name}"
    kptboard = "${var.kptboard}"
  }
}

resource "aws_iam_policy" "kptboard_ssm" {
  name = "${var.kptboard}-ssm"
  policy = "${data.template_file.kptboard_ssm_policy.rendered}"
}

と書くことでIAM Policyのjsonを別ファイルとして切り離せる

一時的にECRリポジトリを作らない

variable.tf
variable "aws_ecr_repository_create" {
  default = "true"
}
aws_ecr_repository.tf
resource "aws_ecr_repository" "kptboard" {
  count = "${var.aws_ecr_repository_create ? 1 : 0}"
  name = "${var.kptboard}"
}

リポジトリ以外の部分をterraform destroyしてterraform applyした時に構築できるかを確認するのに、ECRリポジトリが消えてしまうとDocker Imageを再アップロードしないといけなくなって手間になる。
countを0にすることで実行されないで済む。

サンプル

kptboardというrailsアプリケーションをECSで動かす用のterrafromのソースを置きました。

https://github.com/f96q/fastladder-terraform

続きを読む

自動Blue-Greenデプロイをansibleで構築してみた

自己紹介

一年目のインフラエンジニア
クラウドなんだからBlue-Greenデプロイをやりたいと言われ、構築して運用してみた。

Blue-Greenデプロイの構築方法

Blue-Greenデプロイの概要

AWSで環境を構築するにあたり以下の図のように設計してみました。
この図の通り、ALBにてルーティングしているターゲットグループに紐づいているEC2を切り替えることで、Blue Greenデプロイメントを行っています。
AWSのホワイトペーパー(p. 32)にもあったのですが、DNSのルーティングを行う方法に比べ、Rollbackにおけるリスクを減らすことができます。

デプロイのために作成したplaybook

実際にansibleでplaybookを組んでみました。
なんかrolesがたくさんあるのは、ひとつひとつのrolesをシンプルにしたかったからです。
なぜなら、rolesが複雑になるとあとで管理が面倒になる、と先輩に言われたからだったり、、、
(今はとても実感しています。本当に分けた方がいい。。。)

---
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prd"}

playbookの解説

  • EC2立ち上げ

まずはEC2を立ち上げるrolesを作成、ここは後々他のデプロイ機能でも活用できるように切り出してみました。セキュリティ的な意味で、userはrootではなく別にユーザーを作成。

provision.yml
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2

※EC2を立ち上げてRoute53に登録、SSH接続の確認まで行うrolesを例として載せておきます。変数は別のplaybookで指定しています。(気になるところあればコメントください。)

roles/ec2/tasks/main.yml
- name: Provision EC2 Box
  local_action:
    module: ec2
    key_name: "{{ ec2_keypair }}"
    group_id: "{{ ec2_security_group }}"
    instance_type: "{{ ec2_instance_type }}"
    image: "{{ ec2_image }}"
    vpc_subnet_id: "{{ ec2_subnet_ids[(inventory_hostname_short[-1:] | int % 2 )] }}"
    region: "{{ ec2_region }}"
    assign_public_ip: no
    instance_profile_name: "{{ ec2_iam_role | default }}"
    wait: true
    user_data: "{{ lookup('template', 'roles/ec2/templates/user_data_hostname.yml.j2') }}"(※hostnameを動的に付けたかったのでtemplateを作成しusr_dataを使用)
    exact_count: 1
    count_tag:
      Name: "{{ inventory_hostname }}"
    instance_tags:
      Name: "{{ inventory_hostname }}"
      Environment: "{{ ec2_tag_Environment }}"
      Service: "{{ service_name }}"
    volumes:
    - device_name: /dev/xvda
      device_type: gp2
      volume_size: "{{ ec2_volume_size }}"
      delete_on_termination: true
  register: ec2

- debug: var=item
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Setup for DNS in AWS Route 53
  route53:
    aws_access_key:
    aws_secret_key:
    command: create
    private_zone: True
    zone: bdash.inside
    record: '{{ inventory_hostname }}.bdash.inside'
    type: A
    ttl: 60
    value: '{{ item.private_ip }}'
    overwrite: yes
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Wait for the instances to boot by checking the ssh port
  wait_for:
    host: '{{ item.private_ip }}'
    port: 22
    delay: 60
    timeout: 320
    state: started
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode
  • ミドルウェアからデプロイまで

webアプリはrailsで組まれているので、よくある組み合わせのnginx、unicornで構築しました。
DBはインフラで管理するので、database.ymlを作成して配置するrolesを別途作成しています。
あとは、デプロイしただけでmigrationまで行ってあげる親切設計。

ちなみにrelease-testのrolesは疎通確認、ヘルスチェックを行うrolesです。
もしうまくデプロイからnginx,unicornの起動までできなかった場合はansibleが落ちます。
これでデプロイしたけど、見てみたら動いていないなんてことはない。

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
  • デプロイ後

デプロイ後にターゲットグループの切り替えを行います。
ここでBlue-Greenデプロイを実現しています。ターゲットグループに登録して、削除するまで行いました。

ただ、このrolesは本番だけは行わず、手動で切り替えています。なぜなら、削除を行ってしまうと問題が起きた時にロールバックしにくくなる、と言うのが一番の理由です。うまくやればできるとは思うので、残論点です。

開発環境はそんなの関係ないので、利便性を取っています。

    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prduction"}

いざ、運用!!

ここまでで無事、Blue-Greenデプロイが実現できました。
当初の予定通り、ターゲットグループが自動で切り替わり、外部からの接続も確認できました。
そして、ansibleで構成管理をしているためサーバーを作っては壊すこともできます。
つまり、Immutable Infrastructureも実現できており、データを作っては壊す開発環境にはもってこいなものができました。

追加で実装に組み込んだテストrolesによって、「ansibleが通ったけれどサービスが動いていない!」なんてぬか喜びもなくすことができました。

まさに、いいことずくめ!!
自動デプロイにひとつ近づいた!

と思っていたのですが、ここで更なる問題が、、、

切り替え後にterminate処理をいれたら、アプリのdaemonなどのプロセスが強制KILLされる。

たとえばアプリ側でデータ出力や取込に数時間かかるという話はよくある話です。
そんな時にターゲットグループ切り替えだけならいざしらず、
サーバーを落としてしまったのならプロセスが強制的にKILLされ、結果処理停止となります。

自動デプロイを実装した結果、アプリ側に影響を出してしまいました。
この辺りはBlue-Greenデプロイを行う上で考慮すべき項目でしたね。

このことからプロセス監視を行うことが、今後の課題かなと思っています。
AWSにはスポットインスタンスもあるので、terminateしても動き続けられるようアプリ側にサルベージできる機能か何かも欲しいですね。

マイクロサービスだったことを忘れていた

運用に載せてからしばらくたつと、サービスも増え立てるサーバーも増えてきます。
つまり、、、デプロイの回数が単純に増えていきます。
更に自動化を進めないと、工数が増加の一途をたどることに・・・

最後に

自動デプロイ、Blue-Greenデプロイをansibleで実装してみました。
サービスのリリーススピードはデプロイの手間に依存する」が私の持論です。
なので、デプロイ作業をいかに自動化しつつ安定化させるかが肝だと思っています。

まだまだ改善できるところは多いので、今週末もansibleと一緒に過ごします~

続きを読む

CentOS 7でAmazon EBSのボリュームを拡張する

Amazon EC2インスタンスとしてCentOS 7を選んだ場合にAmazon Elastic Block Store(EBS)のボリュームを拡張する方法です。

本稿では以下の手順に従ってその方法を説明します。

  1. Amazon EBSボリュームを拡張する
  2. ブロックデバイス(/dev/xvda1)のサイズを拡張する

確認した動作環境

  • OS(EC2インスタンス): CentOS Linux release 7.3.1611 (Core)

前提条件

ファイルシステムは CentOS 7標準の「XFS」 であることを前提とします。

ファイルシステムの種類は以下のコマンドを入力することで確認することができます。

$ df -Th
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/xvda1     xfs        16G   16G   0G  99% /
...
...
...

dfコマンド

df コマンドは、システムのディスク領域の使用量についての詳しいレポートを表示します。

18.4. ブロックデバイスとファイルシステムの表示 – Red Hat Customer Portal

-Tはファイルシステムの種類を表示するオプションです。

Amazon EBSボリュームを拡張する

Amazon EBSボリュームの料金体系を確認する

Amazon EBSボリュームを拡張した場合、どれくらいの費用がかかるのかを事前に確認しておきましょう。

リージョンが「アジアパシフィック(東京)」の場合(2017年11月27日現在)

ボリューム 料金(ドル) 料金(円) 単位
Amazon EBS 汎用 SSD (gp2) ボリューム $0.12 12.6円(105円/ドルとした場合) 1 か月にプロビジョニングされたストレージ 1 GB あたり

「Amazon EBS 汎用 SSD (gp2) ボリューム」の場合、I/Oはボリュームの料金に含まれているため、 プロビジョニングした各ストレージのGBに対してのみ課金されます。

最新の料金体系は「料金 – Amazon Elastic Block Store(ブロックストレージ)|AWS」から確認することができます。

「Amazon EC2 コンソール」からEBSのボリュームサイズを変更する

コンソールからEBSのボリュームサイズを変更する手順は
コンソールからの EBS ボリュームの変更 – Amazon Elastic Compute Cloud」から確認することができます。

これまでの手順でEBSのボリュームサイズを変更することができますが、続いてブロックデバイスのサイズを拡張する必要があります。

ブロックデバイス(/dev/xvda1)のサイズを拡張する

ブロックデバイスのサイズを確認する

以下のコマンドを入力してブロックデバイスのサイズを確認しましょう。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  32G  0 disk
└─xvda1 202:1    0  16G  0 part /

上記の例ではxvda1のサイズが16Gであることが分かります。

lsblkコマンド

lsblk コマンドを使用すると、利用可能なブロックデバイスの一覧を表示できます。

一覧表示された各ブロックデバイスについて lsblk コマンドが表示するのは次のとおりです。デバイス名 (NAME)、メジャーおよびマイナーデバイス番号 (MAJ:MIN)、リムーバブルデバイスかどうか (RM)、そのサイズ (SIZE)、読み取り専用デバイスかどうか (RO)、そのタイプ (TYPE)、デバイスのマウント先 (MOUNTPOINT) です。

18.4. ブロックデバイスとファイルシステムの表示 – Red Hat Customer Portal

ブロックデバイスを拡張する

それでは以下のコマンドを入力してブロックデバイスを拡張しましょう。

$ sudo xfs_growfs /dev/xvda1

xfs_growfsコマンド

xfs_growfs コマンドを使用すると、マウント中の XFS ファイルシステムを拡大することができます。

-D size オプションでファイルシステムを指定の size (ファイルシステムのブロック数) まで大きくします。 -D size オプションを指定しない場合、xfs_growfs はデバイスで対応できる最大サイズにファイルシステムを拡大します。

6.4. XFS ファイルシステムのサイズの拡大 – Red Hat Customer Portal

もう一度以下のコマンドを入力してブロックデバイスのサイズを確認しましょう。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  32G  0 disk
└─xvda1 202:1    0  32G  0 part /

xvda1のサイズが16Gから32GBに変更されたことが分かります。


クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。

続きを読む

AWS CloudFormation で RDS (SQLServer) インスタンスを作成してみる

0.はじめに

ずっと AWS CloudFormation 使いたいなと思っていたので、
使ってみました。

1.事前準備

  1. RDS のサブネットグループを作成しておく。

  2. セキュリティグループを作成しておく。

2.AWS CloudFormation で RDS(MSSQL) を作成

  1. AWS CloudFormation のマネジメントコンソールを開く。

  2. 「スタックの作成」ボタンを押下する。
    • 0001.jpg

  3. 「スタックの作成 – テンプレートの選択」画面が表示されるので、「テンプレートの選択」→「テンプレートを Amazon S3 にアップロードする」をチェック、以下の json ファイルをアップロードし、「次へ」ボタンを押下する。

    • 0002.jpg
    GS-RDS-MSSQL-from-bak.template
    {
    "AWSTemplateFormatVersion": "2010-09-09",
    "Parameters": {
        "ProjectName": {
            "Type": "String",
            "Default": "[プロジェクト名]"
        },
        "DbSubnetGroupName": {
            "Type": "String",
            "Default": "[作成しておいたサブネットグループ名]"
        },
        "VpcSecurityGroup": {
            "Type": "AWS::EC2::SecurityGroup::Id",
            "Default": "[作成しておいたセキュリティグループのID]"
        }
    },
    "Resources": {
        "DbInstance": {
            "Type": "AWS::RDS::DBInstance",
            "Properties": {
                "Engine": "sqlserver-ex",
                "DBInstanceClass": "db.t2.micro",
                "AllocatedStorage": "20",
                "StorageType": "gp2",
                "DBInstanceIdentifier": "[DBインスタンス名]",
                "MasterUsername": "[ユーザーID]",
                "MasterUserPassword": "[パスワード]",
                "DBSubnetGroupName": {
                    "Ref": "DbSubnetGroupName"
                },
                "PubliclyAccessible": true,
                "AvailabilityZone": "ap-northeast-1a",
                "VPCSecurityGroups": [
                    {
                        "Ref": "VpcSecurityGroup"
                    }
                ],
                "CopyTagsToSnapshot": true,
                "BackupRetentionPeriod": 7,
                "Tags": [
                    {
                        "Key": "[タグの名前]",
                        "Value": "[タグの値]"
                    }
                ]
            },
            "DeletionPolicy": "Snapshot"
        }
    }
    }
    
  4. 「スタックの作成 – 詳細の指定」画面が表示されるので、以下の項目を入力し、「次へ」ボタンを押下する。

    • スタック名称 : ※任意

    • 0003.jpg

  5. 「スタックの作成 – オプション」画面が表示されるので、「次へ」ボタンを押下する。

    • 0004.jpg

  6. 「スタックの作成 – 確認」画面が表示されるので、「作成」ボタンを押下する。

    • 0005.jpg

  7. スタックの一覧が表示されるので、作成したスタックの状況を確認します。また RDS のマネジメントコンソールも開き、作成する RDS インスタンスの状況を確認します。

    • 0006-1.jpg

    • 0006-2.jpg

  8. しばらくすると、作成が完了します。

    • 0007-1.jpg

    • 0007-2.jpg

99.ハマりポイント

XX.まとめ

とりあえず使ってみたかったので、
ちゃんと作成されてよかったです。

AWS のリソースのみだけではなく、
それ以外のリソースや OS 内部の設定など、
そういった設定も必要な場合での CloudFormation の使い方ってどうなるんでしょうかね?

その辺も今後試せればと思います。

続きを読む

Terraform の Vault Provider を試す on AWS

Terraform 0.8 から追加されている Vault Providerを使用して
AWS の Credential を tfvars や環境変数ではなく、Vault から読み込んでみます。

Vault とは

Hashicorp社がリリースしている機密情報(secret)を管理するためのツールです。
詳細についてはこちらのタグにわかりやすい記事がたくさんありますのでご参照ください。
https://qiita.com/tags/Vault

やってみる

Static Secretの作成

Vault を開発モードで起動します。
開発モードではメモリ上に secret が記録され、停止時には消去されます。
$ vault server -dev ※フォアグラウンドで実行され続けます

クライアントからvaultを操作するため、VAULT_ADDR環境変数を設定します。
$ export VAULT_ADDR=http://127.0.0.1:8200

※開発モードではTLSなし、また起動時点で Unseal、root でログインされた状態です。
実際の環境では Vaul tへのアクセスは Unseal Key よるロック解除とユーザ認証が必要になります。
このあたりのイメージは公式の Interactive Tutorial を体験していただくとよいかと思います。

AWS のアクセスキー/シークレットアクセスキーを静的に書き込みます。

$ vault write secret/aws_test access_key=xxxxxxxxxxxxxx secret_key=xxxxxxxxxxxxxxxxxxxx
Success! Data written to: secret/aws_test

shell の history に残したくない場合は、JSON で記述し、@ でファイルを指定します

$ vault write secret/aws_test @data.json

TFファイルを作成する

vault_generic_secret データソースを使用します。
Terraform Module Registryec2-instance モジュールを使用して、EC2を起動しました。

main.tf
data "vault_generic_secret" "aws_test" {
  path = "secret/aws_test"
}

provider "aws" {
  access_key = "${data.vault_generic_secret.aws_test.data["access_key"]}"
  secret_key = "${data.vault_generic_secret.aws_test.data["secret_key"]}"
  region     = "ap-northeast-1"
}

# fileter latest AMI
data "aws_ami" "amazon_linux" {
  most_recent = true

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

  filter {
    name = "owner-alias"
    values = [
      "amazon",
    ]
  }
}

module "ec2-instance" {
  source = "terraform-aws-modules/ec2-instance/aws"

  name  = "aws_test_ec2"
  count = 1

  ami                    = "${data.aws_ami.amazon_linux.id}"
  instance_type          = "t2.micro"
  key_name               = "keyname"
  vpc_security_group_ids = ["sg-12345678"]
}

実行

$ terraform init
$ terraform plan
$ terraform apply

plan結果の一部

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

data.vault_generic_secret.aws_test: Refreshing state...
data.aws_ami.amazon_linux: Refreshing state...
以下略..

Vault が Unseal されていない状態では以下のようなエラーが出力されます

$ 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.

Error refreshing state: 1 error(s) occurred:

* provider.vault: failed to create limited child token: Error making API request.

URL: POST http://127.0.0.1:8200/v1/auth/token/create
Code: 503. Errors:

* Vault is sealed

注意点

Vault から読み取ったデータは tfstate には平文で記録されてします。。。
tfstate の管理は厳密に行う必要があります。

参考URL

https://www.vaultproject.io/intro/getting-started/dev-server.html
https://www.terraform.io/docs/providers/vault/d/generic_secret.html
https://www.hashicorp.com/blog/terraform-0-8.html#vault

以上です。

続きを読む

AWS NATインスタンスを作成したメモ

AWSでEC2とWorkspacesを1つのVPCに入れて NATを構成しました。
昔とった杵柄でちょろいと思いきや、色んなところにハマりまくったので、記録しておきます。

要件

  • VPC内にEC2数台と、Workspaces数台を構築
  • VPC内でEC2とWorkspacesは相互に通信する
  • EC2とWorkspacesからVPC外(インターネット)にアクセス可能
  • VPC外から一部のEC2 Webサーバーにアクセスできる(ポート転送)

完成形

以下、アドレスやポート番号、IDなどはダミーです。
また、設定する順番通りには記載していないので、手順については以下のサイトの通りです。

http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_NAT_Instance.html

構成図

Qiita.png

NAT instanceの設定

インスタンスの作成

今回は、「amzn-ami-vpc-nat-hvm-2015.03.0.x86_64-gp2」というAMIから作成しました。
コミュニティAMIで「amzn-ami-vpc-nat」で検索して一番上にあったのを素直に選択しました。

送信元/送信先チェックを無効にする

EC2 コンソールで NATインスタンスを選択して、「ネットワーキング」→「送信元/送信先の変更チェック」で無効にしておく。

ポート転送

デフォルトだと無効になってます。

$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0

有効にします。

$ sudo sysctl net.ipv4.conf.all.forwarding=1
net.ipv4.conf.all.forwarding = 1 

NATの設定

iptablesを書きます。

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 192.168.0.0/16 -o eth0 -j MASQUERADE
-A PREROUTING  -i eth0 -d 192.168.10.100 -p tcp -m tcp --dport 80   -j DNAT --to 192.168.32.101
-A PREROUTING  -i eth0 -d 192.168.10.100 -p tcp -m tcp --dport 8080 -j DNAT --to 192.168.32.102:8080
-A POSTROUTING -o eth0 -d 192.168.32.101  -p tcp -m tcp --dport 8080 -j SNAT --to-source 192.168.10.100
-A POSTROUTING -o eth0 -d 192.168.32.102  -p tcp -m tcp --dport 80   -j SNAT --to-source 192.168.10.100
COMMIT

サービス再起動すると反映されます。

$ sudo service iptables restart

VPCの設定

IPv4 CIDRブロック 192.168.0.0/16

サブネット

名前 CIDR
Public 192.168.0.0/20
Private1 192.168.32.0/21
Private2 192.168.48.0/21

ルートテーブル

サブネット 送信先 ターゲット
Public 192.168.0.0/16 local
0.0.0.0/0 igw-xxxxxxxx(*1)
Private1 192.168.0.0/16 local
0.0.0.0/0 eni-xxxxxxxx(*2)
Private2 192.168.0.0/16 local
0.0.0.0/0 eni-xxxxxxxx(*2)

(1) ゲートウェイを指定する
(
2) NAT Instanceを指定する

セキュリティグループの設定

AWSの推奨を参考にしつつこんな感じ。

ポート範囲 ソース
すべてのTCP 192.168.0.0/16
すべてのICMP – IPv4 192.168.0.0/16
80 (参照元Global IP)
8080 (参照元Global IP)
22 (管理者のGlobal IP)

ハマったこと

起動済のEC2のVPCを変更できない

オンプレだと、ネットワークケーブル引っこ抜いて挿し直せば良いだけなので、意外と罠。
一度、AMIを作成し、設定しておいたVPCでイメージからインスタンスを起動すればOKです。

EC2を作り直したらSSH接続できなくなった

NAT関係ないですが。
AMIからEC2インスタンスを作り直すと finger printが変わるため、sshするとエラーになります。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
(後略)

すいません、NASTYなことしたのは、私です。
ローカルの .ssh/known_hostsを開いて、IPアドレスで接続先を特定し、その行を削除すればOKです。

Workspacesには、subnetが3つ必要

Public 1つと、Private 2つが必要でした。
NAT Instanceのドキュメントを見て Subnet 2つで進めてたら、Workspaces立てるところでドボンしました。
ちなみに、Workspacesのドキュメントには明記されていましたので、計画性のある向きはハマらないと思う。

DockerホストもIP転送を有効にする必要あり

これも単に再起動したから発生した問題ですが、ちょっとハマった。
EC2上でDockerコンテナを立てていたインスタンスで、IP転送が無効になっていたため、コンテナから通信できなくなっていました。

NAT instanceにはVPC内のローカルIPで入ってくる

当初iptablesでこんな風に書いていたのですが、ポート転送されずに???となりました。

-A PREROUTING  -i eth0 -d 118.123.123.123 -p tcp -m tcp --dport 80   -j DNAT --to 192.168.32.101

tcpdumpで見てみて、NAT instanceに入ってきた時点で、VPC内のローカルIPになっていることが判りました。
結果として、前記の設定でポート転送に成功しました。

感想

AWSが良きに計らってくれるところと、自分でケアしなければいけないところの境界線がハマりポイントでした。

続きを読む

EBS学習メモ

この文書について

AWS EBSに関する学習メモ

https://aws.amazon.com/jp/ebs/details/

EBS

簡単なまとめ

  • SSD(io1,gp2)とHDD(st1,sc2) に大別される
  • 読み書きの瞬発力(IOPS)がほしいなら io1
  • そこそこで良いなら gp2
  • 安さと最大スループットを求めるなら st1sc1
  • io1でないEBSを使っていて、IOPSで問題が発生したら BurstBalance を確認
    • Burstしていなければ、EBS以外に問題がある
    • Burstしていれば、EBSをio1に変え、EC2もEBS最適化インスタンスにする

性能比較

ボリューム当たりの最大IOPS

io1(2万) > gp2(1万) >>> st1(500) > sc1(250)

ボリューム当たり最大スループット

st1(500MB/s) > io1(320MB/s) > sc1(250MB/s) > gp2(160MB/s)

I/O サイズとボリュームのスループット制限

  • EBSにはスループット制限がある
  • 小さいデータのI/O操作が原因で、スループット制限よりも低いデータ量でも制限が発動することがある
    • 特にSSDの場合、単一I/O とみなされるデータ量が小さい
  • スループット制限を超えてもバーストバケットを使ってカバーされる
    • バーストバケットがなくなると本当に制限される

      • CloudWatchなどでBurstBalanceを見ると状況がわかる
  • EC2 インスタンスの帯域幅が制限要因になることがある
    • 対策はEBS最適化インスタンスの利用

IPOS

  • IOPS = 1 秒あたりの入出力操作数を表す測定単位
  • 操作は KiB 単位で測定される
  • 単一I/O とみなされるデータ量はEBSがSSD/HDDによって異なる
    • 最大I/Oサイズ

      • SSD: 256 KiB
      • HDD: 1,024 KiB
    • SSDでは1つのIOとみなさるサイズが小さい
      = I/O やランダム I/O の処理が HDDより効率が良い

EBS最適化EC2インスタンス

http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html

  • io1 は EBS最適化EC2との併用が推奨
  • EBS最適化EC2インスタンスの特徴
    • Amazon EC2 と Amazon EBS の間の専用スループットがある

      • Amazon EBS I/O と EC2 インスタンスからの他のトラフィックとの競合を最低限に抑える
      • 専用スループットは500Mbps~10,000 Mbps
        • インスタンスタイプに応じて変化
  • EBS最適化インスタンスが使えるEC2 (2017年10月現在)
    • c系 (c1, c3, c4)
    • d系 (d2)
    • f系 (f1)
    • g系 (g2, g3)
    • i系 (i2, i3)
    • m系 (m1, m2, m3, m4)
    • p系 (p2)
    • r系 (r3, r4)
    • x系 (x1, x1e)

続きを読む