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)が取得できているのがわかります。

その他の参考

続きを読む

upgrade amazon aws from Micro to Medium

さらに表示: aws add volume to running instance, ec2 change instance type while running, migrate ec2 instance to another region, aws resize ebs volume, aws change instance type greyed out, any data on the ephemeral storage of your instances will be lost., aws expand root volume windows, resize … 続きを読む

re:Invent 2017 TUESDAY NIGHT LIVEまとめ

Fusic Advent Calendar 2017 の3日目の記事です。

re:Invent 2017では下記の3つの基調講演で様々なサービスや新機能が発表されました。
・TUESDAY NIGHT LIVE
・KEYNOTE DAY1
・KEYNOTE DAY2

本日はTUESDAY NIGHT LIVEについてまとめました。

TUESDAY NIGHT LIVE

大きく下記の3テーマが語られます。
・COMPUTING AT SCALE
・LOAD BALANCING AT SCALE
・SECURIRY AT SCALE

「SCALE」という言葉が強調されてますね。

ANATOMY OF AN AWS REGION

https://www.youtube.com/watch?time_continue=124&v=dfEcd3zqPOA#t=7m23s

・AWSは最初の5年で4リージョン、次の5年で7リージョンを開設してきました。(10年で11リージョン)
・そして、2016年、2017年、2018年でさらに11リージョンを開設します。
・これらのリージョン間のネットワークを強化してグローバルなよりネットワークにしていく

COMPUTING AT SCALE

https://www.youtube.com/watch?time_continue=124&v=dfEcd3zqPOA#t=14m01s

・ここ数年でGPU & FPGAの利用が急成長している
・P3インスタンスの利用方法についての説明
 -> 機械学習
  -> 自動運転に関する企業の紹介

 1. ハードウェアの進化
 2. 機械学習フレームワーク
 3. 急成長するコミュニティ(GLUON)

AMAZON EC2 INSTANCES

https://www.youtube.com/watch?time_continue=124&v=dfEcd3zqPOA#t=27m33s

・Amazon EC2の基本的なアーキテクチャの説明
・EC2の進化
 1. EC2 Hosts
 2. EC2 Managment Services
 3. Amazon EBS
 4. Nitro System(AWS独自開発のクラウド基盤)

・EC2の目標
1. セキュリティ
 AWSの最も大事な品質はセキュリティとも語っている
2. パフォーマンス
 EC2インスタンスパフォーマンスは、他のパフォーマンス全てに関わる重要事項
3. 親しみやすさ

・Nitro System
1. C3インスタンス
 -> ネットワークパケットのプロセスをNitro Systemに移行
 -> レイテンシーの50%向上
2. C4インスタンス
 -> ストレージのプロセスをNitro Systemに移行
 -> EBS最適化がデフォルト化
 -> 12.5%計算力向上
3. C5インスタンス
 -> Custom Nitro ASICを選択
 -> Annapurna labsが開発
 -> Custom Nitro ASICを導入したのがC5インスタンス(Nitro Hypervisor)
  -> コアの技術はKVM
4. Bare Metal Instances

例) AUTODESK

https://www.youtube.com/watch?time_continue=124&v=dfEcd3zqPOA#t=48m44s
・機械学習による設計
・GENERATIVE DESIGN
・GENERATIVE DESIGNによって飛行機のドアなどを設計

https://www.autodesk.co.jp/solutions/3d-design-software?referrer=%2Fsolutions%2F3d-design-software

LOAD BALANCING AT SCALE

https://www.youtube.com/watch?time_continue=124&v=dfEcd3zqPOA#t=1h04m32s
・AWS最初のLOAD BALANCING紹介(32MB)

THE GOLDEN AGE PF HARDWARE LOAD BALANCIERS

・GOOD
・信頼性、スケーラビリティ、俊敏性を実現する簡単な方法
・プロバイダの増加、パフォーマンスの向上、機能の向上、コストの削減

・BAD
・ロードバランサーのコストの割合が増加している
・運用上の課題

ロードバランサーの問題点① BLACK BOX

・ハードウェアがブラックボックス化しており、問題を課題を解決できない
・他のユーザの真ん中ぐらいで利用して最初に問題に直面しないようにしていた

ロードバランサーの問題点② Virtual IPの数

・設定が煩雑
・10年前のVIP数 6,000
・現在のVIP数 600,000

Hyperplane

HARDWARE LOAD BALANCIERS
・2Nアーキテクチャでは最大で50%の利用率になる

S3 LOAD BALANCIERS
・Hyperplane(S3 分散型 ロードバランサー)

下記のサービスで利用されている
1. Amazon Elastic File System(EFS)
2. AWS Managed NAT
3. AWS Network Load Balancer(NLB)
4. AWS PrivateLink

PrivateLink (個人的に大注目)

・インターネットを介さずにサービスにアクセス可能
・複数アクセス間のVPCに対してサービス提供が可能
・APNはもっと簡単にサービス提供が可能

SECURIRY AT SCALE

https://www.youtube.com/watch?time_continue=124&v=dfEcd3zqPOA#t=1h22m50s
・自動化とツール化
・機械学習
・ベストプラクティス
・パートナー

・開発者もセキュリティエンジニアでもある
・AWSはセキュリティ対策を自動化している

AMAZON MACIE

・データアクセスアクティビティのモニタリング

AMAZON GuardDuty

・AWSアカウント全体の監視

※ セキュリティのポイント:人をデータから引き離す
※ AWSの文化はセキュリティを非常に大切にする

参考サイト
https://aws.amazon.com/jp/about-aws/events/reinvent2017-1128/
https://dev.classmethod.jp/cloud/aws/aws-reinvent-tuesdaynightlive-engineers-eye/
https://dev.classmethod.jp/cloud/aws/aws-reinvent-2017-keynote-day-1/
https://dev.classmethod.jp/cloud/aws/aws-reinvent-2017-keynote-day-2/
https://www.youtube.com/playlist?list=PLhr1KZpdzukfZxFGkA796dKaUufAgnU4c

続きを読む

tsort について

はじめに

Ruby Advent Calendar 2017 5日目の記事です。

この記事では、Ruby標準ライブラリにある tsort について、説明します。
tsort を使うことで、依存関係を解決して、順番に処理することなどが簡単にできます。

今回の内容は Meguro.rb #9 での発表資料をベースにしています。

トポロジカルソートとは

  • グラフ理論でのアルゴリズムの1つ
  • 依存関係を順に処理したいときに使える
  • Ruby 標準ライブラリの tsort でトポロジカルソートができる

トポロジカルソートの利用例

Set#divide

標準ライブラリの Set#divide は内部実装で tsort を使っています。

その前に Set#divide について説明しましょう。
以下、るりまでの説明です。

元の集合をブロックで定義される関係で分割し、その結果を集合として返します。

ブロックパラメータが 1 個の場合、block.call(o1) == block.call(o2) が真
ならば、o1 と o2 は同じ分割に属します。

ブロックパラメータが 2 個の場合、block.call(o1, o2) が真ならば、
o1 と o2 は同じ分割に属します。
この場合、block.call(o1, o2) == block.call(o2, o1)
が成立しないブロックを与えると期待通りの結果が得られません。

o1 と o2 が同じ分割に属し、 o2 と o3 が同じ分割に属する場合、 o1 と o3 がたとえブロックの評価結果が偽であっても、 o1 と o3 は同じ分割に属することになります。

下記の例をみると分かりやすいでしょう。

require 'set'
numbers = Set[1, 3, 4, 6, 9, 10, 11]
set = numbers.divide { |i,j| (i - j).abs == 1 }
p set     # => #<Set: {#<Set: {1}>,
          #            #<Set: {11, 9, 10}>,
          #            #<Set: {3, 4}>,
          #            #<Set: {6}>}>

9 と 11が同じ分割に属しています。

Rubygems

Rubygems は内部で依存関係を解決する処理があり、そのために tsort を利用しています。

Bundler

Bundler も Rubygems と同様に内部で依存関係を解決するため、tsort を使っています。

Ruby on Rails

Ruby on Rails の Railtie では tsort を使っています。

ドキュメント によると、

Railtie is the core of the Rails framework and provides several hooks to extend Rails and/or modify the initialization process.

すべての Rails のコンポーネント(Action Mailer, Action Controller, Action View and Active Record)は Railtie です。

Ruby on Rails の Initializer では、:before:after オプションを渡すことで、特定の Initializer の前後に順に実行するように指定することができます。

この Initializer の実行順を解決するために内部的に tsort が使われています。

tsort の適用例

今回、tsort が必要だった理由

  • AWS の運用費用を売上管理と同じ分類で管理したかった。
  • AWS では、リソースの費用をタグごとに分類して管理する機能がある
  • とはいえ、やってみたところ未分類のコストが多かった
    • 理由: タグがついていないリソースが多く、それらのコストが未分類として計上されていた
    •   EC2 にはタグがついていたが、EBS、スナップショット、AMI 等にはタグがなかった
  • EC2 に付与されたタグを自動的に関連するリソースにも付与するスクリプトを作ろうと思った

AWS のリソース間の関連

  • EC2 を起点として、リソース間の関連性は下図のようになっています。

    • それぞれのリソースの詳細については、今回の主題と異なるので説明を割愛します。

image.png

処理内容

詳細は、今回説明しませんが、下記の処理を作成しました。

  1. aws describe-instances コマンドで、EC2 と EBS と ENI の関係を取得・EC2 に付与されたタグをすべて取得
  2. aws describe-snapshots コマンドで、EBS と スナップショット ID の関係を取得
  3. aws describe-images コマンドで、AMI と スナップショットID の関係を取得
  4. ①~③の結果生成されたグラフ構造を元に、関連する EC2 に付与されたタグを EBS、ENI、スナップショット、AMI にも同様に付与する

有向グラフの構築

有向グラフを構築する処理の例です。

上記の図を Tree と見立てた場合、ハッシュ @edges の key が親ノード、 value が配列で子ノードとなっています。

@edges = Hash.new{|h, key| h[key] = []}
each_tag_ebs_eni do |instance_id, tags, ebs, eni|
  @edges[instance_id].push *ebs
  @edges[instance_id].push *eni
  
end
each_ebs_snapshot do |volume_id, snapshot_id|
  @edges[volume_id].push snapshot_id
end
each_snapshot_ami do |snapshot_id, ami_id|
  @edges[snapshot_id].push ami_id
end

TSort 利用のための準備

TSort モジュールを include する場合は、tsort_each_nodetsort_each_child メソッドを実装する必要があります。
tsort_each_node ではグラフのすべてのノードに順にアクセスする処理を実装します。
tsort_each_child では、引数で与えられたノードの直接の子のノードに順にアクセスする処理を実装します。
このように、使う側で必要なメソッドを実装するという仕様になっていることで、TSort モジュールは特定のデータ構造に依存しない、柔軟性が高い設計になっています。

class AwsTSort
  include TSort
  def tsort_each_node
    (snip)
  end

  def tsort_each_child(node)
    @edges.fetch(node, []).each do |child|
      yield child
    end
  end
  
end

今回の例では、tsort_each_node が必要なメソッドを利用していませんので、省略しています。

TSort のメソッドの呼び出し

TSort#each_strongly_connected_component_from という少々長い名前のメソッドを使うと、そのノードの子孫を順に処理することができます。
本稿の例では、EC2 に関連した EBS、ENI などのリソースを順に取得できます。

def resources_from(start)
  [].tap do |resources|
    each_strongly_connected_component_from(start) do |nodes|
      resources.push *nodes # nodes は Array
    end
    resources.delete(start) # start自身を除外
  end
end

後日談

TSort を使うことで、無事、各リソースにタグ付けする点については見事実現できました。
しかしながら、それでもなお未分類のコストが一定程度残っている状態です。
AWS的なベストプラクティスは、アカウントの分離だそうですが、今から取り組むのはハードルが高いと感じています。

詳しい人教えてください。

続きを読む

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環境の構築する時に良く使う書き方

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

続きを読む

AWS EC2の容量(EBS)を増やす

EC2の容量を増やしたいときの手順メモ

AWSコンソールで操作

  1. ELASTIC BLOCK STORE
  2. Volumes
  3. 対象のボリュームを選択
  4. Actions
  5. Modify Volume
  6. 容量を指定

ステータスがin-use - completed (100%)になるまで待機

sshでつないで作業

growpartresize2fs を組み合わせてサイズを拡張する

# rootになる
sudo -s

# 現在の容量を確認
# このときにラベル(?)を確認しておく(/dev/xvdaみたいなやつ)
df -h

# 一応こっちも見ておくとよい
lsblk

# 一応こっちも見ておくとよい
fdisk -l

# dry runで確認
growpart  --dry-run /dev/xvda 1

# 問題なければ実行
growpart /dev/xvda 1

# パーティションサイズを拡張
resize2fs /dev/xvda1

# 反映できたか確認
df -h

growpartで指定するのはディスク名なので/dev/xvda1のようにパーティション名を指定してしまうとうまくいかないので注意

# ×これはうまくいかない
growpart  --dry-run /dev/xvda1 1

続きを読む