中途入社のAWSエンジニアが、稼働中サービスの運用状況をキャッチアップするために意識すること

Classiアドベントカレンダー11日目です。
今回は、AWSエンジニアが稼働中のAWSの管理アカウントを渡されて、ビクビクしながらキャッチアップを行っていったときのメモになります。

1.TL;DR

AWSアカウントのログイン前に準備できることもあるし、AWSアカウントにログインしてわかることもあるし、サーバーにログインしてわかることもある。それぞれのレイヤーでどういったことを確認するかを意識しながらキャッチアップを進めていきましょう。

2.AWSアカウントログイン前の事前準備

pre_aws.png

サービスが稼働しているのであれば、AWSアカウントにログインせずとも、たくさんの情報をキャッチアップすることができます。1日目から何らかの大きなアウトプットを出さないと解雇するような会社は、(おそらく)存在しない筈です。まずは落ち着きましょう^^;

2-1.ドキュメント読み込み

サービスのインフラにAWSが使われることが多くなったからといって、入社前に経験したAWS運用フローがそのまま活かせる訳ではありません。まずは、前任者や運用中のドキュメント管理ツールの中から、今までどのような運用を行っていたかを確認します。
ドキュメントを見たときに意識する観点としては、

  • フロー型:時間による鮮度の劣化があるドキュメント
  • ストック型:システム仕様など、メンテナンスが求められるドキュメント

どちらの情報であるかを意識して読むことが重要です。
フロー型の情報は、障害などで一時的な対応用にメモっていることもあり、運用の中で解決済みのことがあります。そのため、ストック型のドキュメントを中心に見ることが素早いキャッチアップになると思います。
とはいえ、ドキュメントの全てがメンテナンスされている会社というのは稀だと思いますので、各種ドキュメントを見ながら、仮説程度に自分なりのシステム構成図などを書いてみましょう。
要件定義書や各種構成図の変更履歴、課題管理表、リスクコントロール表といったドキュメント類があるのであれば、目を通しておきましょう。

2-2.運用フローを観察する

サービス側のドキュメントについては、まだ文書化されてることが多いですが、運用系ツールに関しては、ドキュメント化されていないことがあります。今の開発スタイルであれば、何らかのチャットツール(Slack,ChatWork,HipChat)上で、

  • デプロイ
  • 各種の通知
  • 運用Bot

の運用といったことが行われていると思います。また、チャットだけでなく、メールでの運用フローも存在しているかもしれません。
こうした運用系ツールの存在は、今後自分がリファクタするときに、「必須要件ではないが、重宝している」ということで、「リファクタ後にも、あの機能を実装して欲しい」といった声が社内から上がると思います。
そのため、このような運用フローがどこで実装されているかを見極めることが重要になってきます。

2-3.インフラ部分のコード読み

「俺はフルスタックエンジニアだ!」という強い意思がある方は、この時点で稼働中のアプリ側のコードまで読み込んでいただければよいですが、まずは入社前に期待されたであろう、インフラまわりのコード化部分を把握しておきましょう。どのみち、いずれはメンテナンスを任されたり、質問されることが増えてきますので、自分のメンテナンスする部分を優先的に確認しておきましょう。
実サーバーの運用はAWSに任せているので、ここで意識しておくことは、

  • Infrastructure Orchestration Tools:Terraform, CloudFormationなど
  • Server Configuration Tools:Chef,Ansible,Itamaeなど

あたりのコードがgithubなどに保存されているからといって、メンテナンスされていない可能性もあります。
コードの設計方針などを確認するのは当然必要なのですが、コードの変更履歴が年単位で放置されていないかどうかも見ておきましょう。特に、AWS関連のコードについては、担当する人がアプリ側よりも少ないので、構築当初のコードのままなのか、運用されているコードなのかはPRなどで確認しておいた方がよいです。

3.AWSのアカウント内を調査する

aws_kansatsu.png

実際にAWSアカウントにログインしたり、APIで各種設定を確認していきます。Web系サービスであれば、TCP/IPモデルやC/Sモデルを意識しながら、下層レイヤー回りから調査していき、ネットワークがどうせ設定されているかを確認していきます。
おそらく、ここで多くの疑問(場合によっては、絶望)が生まれる段階です。「あれ?ドキュメントにこう記述されているけど、設定上は違うような…」という沼にハマることがあるでしょう。負けないでください、一人で抱え込まずに闇を共有できる仲間を見つけましょう。

3-1.外部システム連携の確認

関連するAWSサービス

VPC関連のサービスを中心に、自AWSアカウント以外の連携がないかの確認を行います。

関連しやすいAWSサービス例)

  • DirectConnect
  • NAT Gateway
  • Peering Connection
  • Customer Gateways
  • Virtual Private Gateways
  • VPN Connections
  • NetWorkACL
  • SecurityGroup
  • EIP

などに、何らかのインスタンスが稼働していて、productionやhonbanなどの文言がついたものがあれば、それはドキュメント上には存在しないが、サービス上何らかの理由で稼働しているものである可能性があります。
自社のサービスがAWSアカウント内だけで完結しているのであればよいのですが、誤ってここのインスタンスなどを削除すると、場合によってはシステム復旧できないぐらいの痛手になるので、慎重に確認していきましょう。
特に、SecurityGroupは、最近でこそInboundルールにDescriptionをつけられるようになりましたが、数年運用されているシステムには、何で利用しているIPアドレスなのかがわからないことがあるので、設定確認中に不明なIPアドレスを見つけたら社内で有識者に聞いてみましょう。

3-2.システム導線の確認

関連するAWSサービス

インスタンス障害があるとユーザー影響がありそうな、システム導線を追っていきます。

関連しやすいAWSサービス例)

  • ELB(CLB,ALB,NLB)
  • CloudFront
  • EC2
  • ElasticCache(redis,memcached)
  • RDS(Aurora,MySQL,PostgreSQL,SQLServer)
  • ElasticSearch
  • DynamoDB
  • S3

各種のインスタンスサイズが適切かを確認するのはもちろんですが、DB関連についてはバックアップ関連の設定がちゃんと行われているかどうかも確認しておきましょう。バックアップウィンドウの世代数やメンテナンスウィンドウの時間が営業時間内になっているとかは、結構ありがちな設定漏れケースになります。パラメータストアの設定については、本番で稼働している設定が正義なので、設計と違う場合は、社内で経緯を追ってみましょう。

3-3.運用導線の確認

関連するAWSサービス

直接のユーザー影響はないものの、バッチ系およびログインやログ連携など、システム運用で必要な運用導線を追っていきます。

関連しやすいAWSサービス例)

  • EC2
  • Lambda
  • ElasticSearch(& kibana)
  • IAM
  • CloudTrail
  • AWS Config
  • CloudWatch Logs
  • S3
  • Glacier

24224というポート開放を見れば、そのシステムはfluentd関連のフローがあるのはほぼ確定なので、ログの発生から可視化ツールおよびバックアップのフローまで追っていきましょう。また、バッチ系のEC2に関しては、最近のAWSだと、FargateやECS、Lambdaなどで定期バッチを行うことも可能なので、単一障害点をなくすことができないか、今後の計画のために、バッチ系が整理されているドキュメントなどを探してみましょう。

4.サーバー内の設定を確認する

server_chosa.png

最近だと、Server Configuration Toolsが大分普及していたり、コンテナ系の運用が発達してきているので、このあたりのキャッチアップ期間が少なくなるのかなと思います。とはいえ、SSH接続を頻繁に行うサーバーや起動時間が長いサーバーだと、コードの設定と異なる部分が出てきていることがあるかもしれません。
OSの設定やミドルウェアのバージョンなど、SSH接続すると確認した方がよいところは多々ありますが、Server Configuration Toolsの設定と異なっていたり、運用中のアラート設定などで差異がでやすそうな部分を以下に記載します。

4-1.各種メトリクス確認

メモリやプロセスの状況は、通常CloudWatchだけではわからないので、MackerelやZABBIXなどの監視ツールエージェントを入れているのであれば、各サーバーのメトリクスを確認しておきましょう。

4-2.稼働プロセスの確認

pstreeなどで、稼働しているプロセスを確認します。SSH接続が禁止されているのであれば、AWSのSSMエージェントなりで確認できないかを検討してみましょう。設計上のソフトウェアスタックに存在しないプロセスが常駐している場合は、何のエージェントが動いているかを追っておきましょう。

4-3.不要なファイルが出力されていないかの確認

ログレベルがデバッグのままだったり、ログファイルのローテートがなされていない場合があり、アラートは上がっていないけど、サーバー内のリソースを侵食しているときがあります。また、生成されるログファイルが小さすぎると、ディスクに余裕がありそうに見えても、inodeが先に枯渇するときもあります。lsofdf -iなどを可視化するなどして、サーバー内のディスク状況を確認しておきましょう。

4-4.同期処理と非同期処理のプロセス確認

同期処理のプロセスは意識しやすいので、監視対象に入っている可能性が高いです。ただ、非同期系プロセス(Rubyだとsidekiq,Pythonだとcelery,PHPだとphp-resqueなど)が監視対象に入っていないこともあるので、どのサーバーで非同期処理が行われているかを把握しておきましょう。

5.まとめ

AWSや他のパブリッククラウドが全盛になった今でも、3層アーキテクチャのシステム構成やOSI7階層などのレイヤーを意識しながらキャッチアップを行うと、システムを俯瞰しながらキャッチアップを進めることができるのではないかなと思います。とはいえ、前任者がコード化しきれなかった部分もある筈なので、そこは社内で過去経緯を知っている人に笑顔で質問をしてみましょう。技術が発達しても、人に蓄積されるノウハウはまだまだ多いので…
AWSエンジニアが転職する際などのご参考になれば。

続きを読む

絶対的に使った方がいいLogstashのMultiple Pipelinesについて書いてみた

はじめに

おはです!
Logstashのフィルタの中でもGrokが好きなぼくが、Advent Calendar11日目を書かせていただきますー
あ、でも今回は、Grokについては書かないですよ!

じゃあ、何書くの?Grokしか脳のないお前が何を書くのさー
そりゃ、あれだよ!Logstash 6.0がGAされたので、待ちに待ったMultiple Pipelinesについて書くしかないでしょ!

てことで、LogstashのMultiple Pipelinesについて、ゆるーく書いていきます( ゚Д゚)ゞビシッ

構成について

今回テストするにあたって使用した構成は以下です。

  • Amazon Linux AMI 2017.09.1 (HVM)
  • Logstash 6.0
  • logstash-input-s3
  • Elasticsearch 6.0
  • Kibana 6.0
  • X-Pack 6.0

ちなみに、もろもろインストールされている前提で記載しています。
もし、インストールする場合は、以下のインストール手順を参考にして頂ければと思います。

Logstashについて

Logstashは、Elasticsearch社が提供するオープンソースのデータ収集管理ツールです。

LogstashのPipelineは、以下のような流れで処理されます。
例として、インプットデータをApacheのログファイルで、ログファイルに対してフィルタをかけ、Elasticsearchにストアするといった流れにしてます。1

logstash01.png

INPUT

様々なデータソースを取り込みたい!そんな要望に応えるべく用意されたのがLogstash。
Logstashは、様々なデータ形式に対応しています。
例えば、ソフトウェアのログ、サーバのメトリクス、Webアプリケーションのデータや、クラウドサービスなど。

FILTER

INPUTしたデータソースをフィルタで解析し、構造化します。
データソースの変換には、正規表現でデータをパースするためのGrokフィルタや、IPアドレスから地理情報を得るためのGeoIPフィルタなど様々なフィルタライブラリが用意されています。

OUTPUT

データを構造化したのち、任意の出力先にデータをストアします。
Elasticsearch以外の出力先も多数提供されているので、環境に合わせてデータをストアします。

Logastashのディレクトリ構成

上記までが処理の流れで、これらの処理を定義するファイルをLogstashでは準備する必要があります。

以下のようにLogstashのディレクトリは構成されており、conf.d配下に定義ファイル(hoge.confと記載)を配置します。

/etc/logstash
  ├ conf.d
  │ └ hoge.conf
  ├ jvm.options
  ├ log4j2.properties
  └ logstash.yml

hoge.confに様々なデータソースに対して定義します。
例えば、AWSのALBのアクセスログとApacheのアクセスログを定義すると以下のようになります。2

hoge.conf
input {
  s3 {
    tags => "alb"
    bucket => "hoge"
    region => "ap-northeast-1"
    prefix => "hoge/"
    interval => "60"
    sincedb_path => "/var/lib/logstash/sincedb_alb"
  }
  file {
    path => "/etc/logstash/log/httpd_access.log"
    tags => "httpd"
    start_position => "beginning"
    sincedb_path => "/var/lib/logstash/sincedb_httpd"
  }
}
filter {
  if "alb" in [tags] {
    grok {
      patterns_dir => ["/etc/logstash/patterns/cloudfront_patterns"]
      match => { "message" => "%{ALB_ACCESS}" }
      add_field => { "date" => "%{date01} %{time}" }
    }
    date {
      match => [ "date", "yy-MM-dd HH:mm:ss" ]
      locale => "en"
      target => "@timestamp"
    }
    geoip {
      source => "c_ip"
    }
    useragent {
      source => "User_Agent"
      target => "useragent"
    }
    mutate {
      convert => [ "time_taken", "float" ]
      remove_field => [ "message" ]
    }
  else if "httpd" in [tags] {
    grok {
      patterns_dir => ["/etc/logstash/patterns/httpd_patterns"]
      match => { "message" => "%{HTTPD_COMMON_LOG}" }
    }
    geoip {
      source => "clientip"
    }
    date {
      match => [ "date", "dd/MMM/YYYY:HH:mm:ss Z" ]
      locale => "en"
      target => "timestamp"
    }
    mutate {
      remove_field => [ "message", "path", "host", "date" ]
    }
  }
}
output {
  if "alb" in [tags] {
    elasticsearch {
      hosts => [ "localhost:9200" ]
      index => "elb-logs-%{+YYYYMMdd}"
    }
  }
  else if "httpd" in [tags] {
    elasticsearch {
      hosts => [ "localhost:9200" ]
      index => "httpd-logs-%{+YYYYMMdd}"
    }
  }
}

このようにデータソース毎にタグを付与し、if文で判定させるといったかたちになってます。
今回は、データソースが少ないからいいですが、更に増やしていくとなると可読性も悪くなるのと、リソースのハンドリングも難しくなってきます。
また、データソースが増えたり、フィルタ変更などの際には、一つの定義ファイルを更新するため、全体に影響を及ぼす可能性があります。。
なんてこった(´□`;)

Multiple Pipelinesになると何がいいのさ

そこで!Multiple Pipelinesの出番なのです!
hoge.confに対して複数のデータソースを組み込んでいたものを、分割することができちゃうのですー
また、リソースの割り当てとして、Worker数などもPipeline毎に割り当てることができるのです!歓喜!!

ここからは、Multiple Pipelinesをどのように使うのかを書いてきます。

Multiple Pipelinesを試してみる。

Logstashのディレクトリ構成は変わらないのですが、新しくpipelines.ymlという定義ファイルを作成します。
また、先ほどのhoge.confをデータソース毎にファイルを以下のように分けます。

  • alb.cfg
  • httpd.cfg

alb.cfgの定義は以下です。

alb.cfg
input {
  s3 {
    bucket => "hoge"
    region => "ap-northeast-1"
    prefix => "hoge/"
    interval => "60"
    sincedb_path => "/var/lib/logstash/sincedb_alb"
  }
}
filter {
  grok {
    patterns_dir => ["/etc/logstash/patterns/cloudfront_patterns"]
    match => { "message" => "%{ALB_ACCESS}" }
    add_field => { "date" => "%{date01} %{time}" }
  }
  date {
    match => [ "date", "yy-MM-dd HH:mm:ss" ]
    locale => "en"
    target => "@timestamp"
  }
  geoip {
    source => "c_ip"
  }
  useragent {
    source => "User_Agent"
    target => "useragent"
  }
  mutate {
    convert => [ "time_taken", "float" ]
    remove_field => [ "message" ]
  }
 }
output {
  elasticsearch {
    hosts => [ "localhost:9200" ]
    index => "elb-logs-%{+YYYYMMdd}"
  }
}

httpd.cfgの定義は以下です。

httpd.cfg
input {
  file {
    path => "/etc/logstash/log/httpd_access.log"
    start_position => "beginning"
    sincedb_path => "/var/lib/logstash/sincedb_httpd"
  }
}
filter {
  grok {
    patterns_dir => ["/etc/logstash/patterns/httpd_patterns"]
    match => { "message" => "%{HTTPD_COMMON_LOG}" }
  }
  geoip {
    source => "clientip"
  }
  date {
    match => [ "date", "dd/MMM/YYYY:HH:mm:ss Z" ]
    locale => "en"
    target => "timestamp"
  }
  mutate {
    remove_field => [ "message", "path", "host", "date" ]
  }
}
output {
  elasticsearch {
    hosts => [ "localhost:9200" ]
    index => "httpd-%{+YYYYMMdd}"
  }
}

ディレクトリ構成は以下になります。

/etc/logstash
  ├ logstash.yml
  ├ conf.d
  │ └ alb.cfg
  │ └ httpd.cfg
  ├ jvm.options
  ├ log4j2.properties
  └ pipelines.yml

ここでやっと登場するpipelines.ymlの定義は以下です。

pipelines.yml
- pipeline.id: alb
  pipeline.batch.size: 125
  path.config: "/etc/logstash/conf.d/alb.cfg"
  pipeline.workers: 1
- pipeline.id: httpd
  pipeline.batch.size: 125
  path.config: "/etc/logstash/conf.d/httpd.cfg"
  pipeline.workers: 1

めっちゃシンプルですね。
呼び出すファイルパスの指定とパラメータの定義を記載するだけです。
データソース単位でpipeline.workersを割り当てられることになったので、柔軟にコアの配分ができるようになりました。
詳細の設定については、settings fileを確認してもらえればと思います。

動かすよ!

今回、サービス起動を前提としているため、pipelines.ymlを読み込ませるには、logstash.confのpath.configをコメントアウトする必要があります。
(ここでハマりました。。公式サイトにサービス起動について書かれているところがみつからず。。)

logstash.conf
# path.config: /etc/logstash/conf.d/*.conf

てことで、起動しますー
これでデータソース単位で動かすことができます。

$ initctl start logstash
logstash start/running, process 3121

おまけ

今回説明していないですが、X-Packを導入することで、MonitoringでPipelineの状況をみることができます。
データの流れを把握したり、分岐などのロジックが有効に働いてるかなども確認することが可能です。

先ほど、alb.cfgを取り込んだ際のPipelineは以下の様に表示されます。

logstash03.png

さいごに

いかがでしたか?
Multiple Pipelinesは、常にデータを取り込んでいるLogstashを支えるための根幹たる機能ではないでしょうか?
ぜひぜひ、みなさんもMultiple Pipelinesを使って、よりよいLogstsah Lifeを送って頂ければとヽ(*゚д゚)ノ

それでは、Advent Calendar11日目を終わりますー
明日の12日目は、”Elasticsearch v1.7 -> v2.4 -> v5.5 と段階的にバージョンアップした話し”ですね!
楽しみだすー


  1. Elasticsearch社の公開されているグローバルIPを利用しています 

  2. 指定しているパスは、環境に合わせて指定してください 

続きを読む

ALB Ingress Controller を使う

この記事では、 ALB Ingress Controller について書きます。

zalando-incubator/kube-ingress-aws-controller については、 Kubernetes2 Advent Calendar 2017 7日目 @mumoshu 先生の記事で、 書かれていますので、そちらを参照して下さい :bow:

WHY

Kubernetes on AWS で運用している場合、 Kubernetes の Service を作成すると、 AWS の Classic Load Balancer が作成されます。 Classic Load Balancer 以外に Application Load Balancer を利用したい場合が以下のような時にあります。

  • http2 を利用したい
  • /blog などリソース毎に向き先を区切る

Kubernetes on AWS を利用する方は既に AWS を使いだおしている方が大半だと思います。既存のアプリケーションを Kubernetes へ移行しようとした際に、 既に ALB(Application Load Balancer) を利用していたのが、 Kubernetes へ移行したら ELB (Classic Load Balancer) になって http2 無くなりましたというのはパフォーマンスにも影響を与えます。

そこで ALB Ingress Controller を利用することで、 ALB が使えます。

ALB Ingress Controller

The ALB Ingress Controller satisfies Kubernetes ingress resources by provisioning Application Load Balancers.

ALB Ingress Controller は、 Kubernetes の ingress を作成したタイミングで、 Application Load Balancer を作成します。

Design

image.png

The following diagram details the AWS components this controller creates. It also demonstrates the route ingress traffic takes from the ALB to the Kubernetes cluster.

Design に ALB が作られるまでの流れと、トラフィックの流れが書かれています。

Ingress Creation

Kubernetes 上に ingress を一つ作った時の流れ

[1]: The controller watches for ingress events from the API server. When it finds ingress resources that satisfy its requirements, it begins the creation of AWS resources.

[1] ALB Ingress Controller は、 Kubernetes の API Server からの Event を監視し、該当の Event を検知したら AWS のリソースを作成し始める。

[2]: An ALB (ELBv2) is created in AWS for the new ingress resource. This ALB can be internet-facing or internal. You can also specify the subnets its created in using annotations.

[2] ALB を作成する。 annotation を指定することで、サブネットやインターネット向けか内部向けかも決めることができる。

[3]: Target Groups are created in AWS for each unique Kubernetes service described in the ingress resource.

[3] ALB の向き先となるターゲットグループは、 ingress に記述された Service ごとに AWS で作成。

[4]: Listeners are created for every port detailed in your ingress resource annotations. When no port is specified, sensible defaults (80 or 443) are used. Certificates may also be attached via annotations.

[4] リスナは、 ingress の annotation で指定したポート用に作成されます。ポートが指定されていない場合、80または443を使用。 ACM も使用することもできる。

[5]: Rules are created for each path specified in your ingress resource. This ensures traffic to a specific path is routed to the correct Kubernetes Service.

[5] 入力リソースで指定された各パスに対してルールが作成され、特定のパスへのトラフィックが正しい Kubernetes の Service にルーティングされる。

Ingress Traffic

This section details how traffic reaches the cluster.

As seen above, the ingress traffic for controller-managed resources starts at the ALB and reaches the Kubernetes nodes through each service’s NodePort. This means that services referenced from ingress resource must be exposed on a node port in order to be reached by the ALB.

ALB から始まり、各サービスの NodePort を通じて Kubernetes ノードに到達するようになっている。 ALB を使ったサービスを公開するためには、 ingress と NodePort を使った Service の二つが必要になる。

How it Works

  • alb-ingress-controller 用の IAM を作成
  • ALB 作る際に、 sg と subnet を自動でアサインされるように、 subnet にタグの設定
  • AWS の IAM 情報と CLUSTER_NAME を secrets に入れる
  • default サーバーという一旦 target group アサインできるテンポラリのサービスを建てる
  • alb-ingress-controller を deploy する

alb-ingress-controller 用の IAM を作成

Role Permissions

AWS を操作するため、専用の IAM が必要になります。必要になるリソースは例と以下に記載されています。

IAM Policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "acm:DescribeCertificate",
                "acm:ListCertificates"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:CreateSecurityGroup",
                "ec2:CreateTags",
                "ec2:DeleteSecurityGroup",
                "ec2:DescribeInstances",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeTags",
                "ec2:ModifyInstanceAttribute",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticloadbalancing:CreateRule",
                "elasticloadbalancing:CreateTargetGroup",
                "elasticloadbalancing:DeleteListener",
                "elasticloadbalancing:DeleteLoadBalancer",
                "elasticloadbalancing:DeleteRule",
                "elasticloadbalancing:DeleteTargetGroup",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:DescribeTags",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:ModifyLoadBalancerAttributes",
                "elasticloadbalancing:ModifyRule",
                "elasticloadbalancing:ModifyTargetGroup",
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:RemoveTags",
                "elasticloadbalancing:SetSecurityGroups",
                "elasticloadbalancing:SetSubnets"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:GetServerCertificate",
                "iam:ListServerCertificates"
            ],
            "Resource": "*"
        }
    ]
}

ALB 作る際に、 sg と subnet を自動でアサインされるように、 subnet にタグの設定

Subnet Selection

ingress の annotation か auto-detection で、 各ALBを作成するサブネットを決定。

  • annotation: alb.ingress.kubernetes.io/subnets に、 subnet ID または NAME タグを使用して指定
  • auto-detection: annotation の指定はなく、自動検出で ALB を作成

auto-detection を有効にするためには、以下の tag を追加します。 ALB を作る際に subnet が二つ必要なため、二つ tag をつける。

  • kubernetes.io/role/alb-ingress=
  • kubernetes.io/cluster/$CLUSTER_NAME=shared

    • $CLUSTER_NAMEalb-ingress-controller.yamlCLUSTER_NAME 環境変数と一致させる必要がある

設定例

image

AWS の IAM 情報と CLUSTER_NAME を Secrets に入れる

namespace name key description
kube-system alb-ingress-controller AWS_ACCESS_KEY_ID credentials of IAM user for alb-ingress-controller
kube-system alb-ingress-controller AWS_SECRET_ACCESS_KEY credentials of IAM user for alb-ingress-controller
kube-system alb-ingress-controller CLUSTER_NAME cluster name
  • 登録方法

k8sec を使って Sercrets に登録します。

$ k8sec set alb-ingress-controller KEY=VALUE -n kube-system
  • 確認
$ k8sec list alb-ingress-controller -n kube-system
NAME            TYPE    KEY         VALUE
alb-ingress-controller  Opaque  AWS_ACCESS_KEY_ID   "hoge"
alb-ingress-controller  Opaque  AWS_SECRET_ACCESS_KEY   "fuga"
alb-ingress-controller  Opaque  CLUSTER_NAME        "Ooops"

ingress に必要になる Default backend サービスを建てる

kubectl Deployments

alb-ingress-controller を使うために必要になる Default backend サービスを建てる。 alb-ingress-controller を利用する ingress は、全て Default backend を指す。

$ kubectl create -f https://raw.githubusercontent.com/coreos/alb-ingress-controller/master/examples/default-backend.yaml

alb-ingress-controller を deploy する

  • alb-ingress-controller manifest ファイルをダウンロードする
$ wget https://raw.githubusercontent.com/coreos/alb-ingress-controller/master/examples/alb-ingress-controller.yaml
  • Secrets に追加したものを manifest file に反映する
        envFrom:
        - secretRef:
            name: alb-ingress-controller
  • AWS_REGION を設定する
- name: AWS_REGION
  value: ap-northeast-1
  • Deploy alb-ingress-controller
$ kubectl apply -f alb-ingress-controller.yaml  
  • log で起動できているか確認できる。
$ kubectl logs -n kube-system 
    $(kubectl get po -n kube-system | 
    egrep -o alb-ingress[a-zA-Z0-9-]+) | 
    egrep -o '[ALB-INGRESS.*$'
[ALB-INGRESS] [controller] [INFO]: Log level read as "", defaulting to INFO. To change, set LOG_LEVEL environment variable to WARN, ERROR, or DEBUG.
[ALB-INGRESS] [controller] [INFO]: Ingress class set to alb
[ALB-INGRESS] [ingresses] [INFO]: Build up list of existing ingresses
[ALB-INGRESS] [ingresses] [INFO]: Assembled 0 ingresses from existing AWS resources

上手く動かない場合ははここを true にすると良い。 AWS の制限で止められている可能性もありえる。

        - name: AWS_DEBUG
          value: "false"

これで ALB Ingress Controller の準備は完了

実際に ALB 作成してみる

alb-ingress-controller にある echo server を元にやってみる。基本的に以下、二点を抑えるだけで ALB
を利用できる。

  • ingress と NodePort を使った Service
  • ingress の annotation の設定

echoservice

alb-ingress-controller にある sample を元に echoserver を建ててみる。

$ kubectl apply -f https://raw.githubusercontent.com/coreos/alb-ingress-controller/master/examples/echoservice/echoserver-namespace.yaml &&
kubectl apply -f https://raw.githubusercontent.com/coreos/alb-ingress-controller/master/examples/echoservice/echoserver-service.yaml &&
kubectl apply -f https://raw.githubusercontent.com/coreos/alb-ingress-controller/master/examples/echoservice/echoserver-deployment.yaml

Namespace を切って、 NodePort で開放する Service と Deployment が作られる。

$ kubectl get all -n echoserver
NAME                             READY     STATUS    RESTARTS   AGE
po/echoserver-2241665424-xm1rt   1/1       Running   0          10m

NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
svc/echoserver   NodePort   100.65.13.23   <none>        80:31509/TCP   10m

NAME                DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/echoserver   1         1         1            1           10m

NAME                       DESIRED   CURRENT   READY     AGE
rs/echoserver-2241665424   1         1         1         10m
  • ingress file を取得する
wget https://raw.githubusercontent.com/coreos/alb-ingress-controller/master/examples/echoservice/echoserver-ingress.yaml
  • annotation の設定(オプション)

Annotations に全部使える ALB の option が書いてある。

alb.ingress.kubernetes.io/scheme: internet-facing # or 'internal'
alb.ingress.kubernetes.io/connection-idle-timeout: # Defauflt 60
alb.ingress.kubernetes.io/subnets: # subnet ID か Name 
alb.ingress.kubernetes.io/security-groups: # sg ID か Name Default 0.0.0.0/0 
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80,"HTTPS": 443}]' # Default 80
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:hoge:certificate/UUID # ACM 利用する場合
alb.ingress.kubernetes.io/healthcheck-path: # Default "/"
alb.ingress.kubernetes.io/healthcheck-port: # Default Traffic port
alb.ingress.kubernetes.io/healthcheck-interval-seconds: # Default 15
alb.ingress.kubernetes.io/healthcheck-protocol: # Default HTTP
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: # Default 5
alb.ingress.kubernetes.io/healthy-threshold-count: # Default 2
alb.ingress.kubernetes.io/unhealthy-threshold-count: # Default 2
alb.ingress.kubernetes.io/successCodes: # Default 200
alb.ingress.kubernetes.io/tags:  # Tag を入れる
  • ingress を建てる
$ kubectl apply -f echoserver-ingress.yaml

とりあえず default のままでいい場合は下記のコマンド

$ kubectl apply -f https://raw.githubusercontent.com/coreos/alb-ingress-controller/master/examples/echoservice/echoserver-ingress.yaml
  • ALB が作成された log を確認して見る
$ kubectl logs -n kube-system 
  $(kubectl get po -n kube-system | 
  egrep -o alb-ingress[a-zA-Z0-9-]+) | 
  egrep -o '[ALB-INGRESS.*$' | 
  grep 'echoserver/echoserver'
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Start ELBV2 (ALB) creation.
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Completed ELBV2 (ALB) creation. Name: hogefuga-echoserver-ech-2ad7 | ARN: arn:aws:elasticloadbalancing:ap-northeast-1:0000:loadbalancer/app/hogefuga-echoserver-ech-2ad7/17fd1481cb40fcc2
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Start TargetGroup creation.
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Succeeded TargetGroup creation. ARN: arn:aws:elasticloadbalancing:ap-northeast-1:0000:targetgroup/hogefuga-31509-HTTP-c3a0606/9914a217042c4006 | Name: hogefuga-31509-HTTP-c3a0606.
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Start Listener creation.
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Completed Listener creation. ARN: arn:aws:elasticloadbalancing:ap-northeast-1:0000:listener/app/hogefuga-echoserver-ech-2ad7/17fd1481cb40fcc2/0fe42e9436e45013 | Port: 80 | Proto: HTTP.
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Start Rule creation.
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Completed Rule creation. Rule Priority: "1" | Condition: [{    Field: "host-header",    Values: ["echoserver.example.com"]  },{    Field: "path-pattern",    Values: ["/"]  }]
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Fetching Targets for Target Group arn:aws:elasticloadbalancing:ap-northeast-1:0000:targetgroup/hogefuga-31509-HTTP-c3a0606/9914a217042c4006
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Fetching Rules for Listener arn:aws:elasticloadbalancing:ap-northeast-1:0000:listener/app/hogefuga-echoserver-ech-2ad7/17fd1481cb40fcc2/0fe42e9436e45013
[ALB-INGRESS] [echoserver/echoserver] [INFO]: Ingress rebuilt from existing ALB in AWS
  • URL を確認
$ kubectl describe ing -n echoserver echoserver
Name:             echoserver
Namespace:        echoserver
Address:          hogefuga-echoserver-ech-2ad7-126540505.ap-northeast-1.elb.amazonaws.com
Default backend:  default-http-backend:80 (100.96.27.7:8080)
Rules:
  Host                    Path  Backends
  ----                    ----  --------
  echoserver.example.com  
                          /   echoserver:80 (<none>)
Annotations:
Events:
  Type    Reason  Age   From                Message
  ----    ------  ----  ----                -------
  Normal  CREATE  2m    ingress-controller  Ingress echoserver/echoserver
  Normal  CREATE  2m    ingress-controller  hogefuga-echoserver-ech-2ad7 created
  Normal  CREATE  2m    ingress-controller  hogefuga-31509-HTTP-c3a0606 target group created
  Normal  CREATE  2m    ingress-controller  80 listener created
  Normal  CREATE  2m    ingress-controller  1 rule created
  Normal  UPDATE  2m    ingress-controller  Ingress echoserver/echoserver

ここからさらに踏み込んで external DNS の設定がありますが今回は、ALB までで閉じます。
最後に cURL で確認して終了です。

$ curl hogefuga-echoserver-ech-2ad7-126540505.ap-northeast-1.elb.amazonaws.com
CLIENT VALUES:
client_address=10.1.93.88
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://hogefuga-echoserver-ech-2ad7-126540505.ap-northeast-1.elb.amazonaws.com:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=hogefuga-echoserver-ech-2ad7-126540505.ap-northeast-1.elb.amazonaws.com
user-agent=curl/7.43.0
x-amzn-trace-id=Root=1-5a2d4e2f-5545b75b74003cd80e5134bb
x-forwarded-for=192.168.100.10
x-forwarded-port=80
x-forwarded-proto=http
BODY:
-no body in request-
  • 最後は、削除
$ kubectl delete ns echoserver
namespace "echoserver" deleted

ALB も削除される。

$ curl hogefuga-echoserver-ech-2ad7-126540505.ap-northeast-1.elb.amazonaws.com
curl: (6) Could not resolve host: hogefuga-echoserver-ech-2ad7-126540505.ap-northeast-1.elb.amazonaws.com

続きを読む

新しいマネージドコンテナサービスAWS Fargateの価格は高いか安いか?ECS/Fargateのコスト最適化を考えてみよう

AWS Fargate Advent Calendar 2017 の10日目の記事です。昨日は 俺はSSHをしたくない ~ Fargate vs ECS+SpotFleet のPrice比較 ~ でした。 ネタ被ったァ!

Fargate の価格

1vCPU 1時間あたり\$0.0506、1GBメモリ 1時間あたり\$0.0127

From https://aws.amazon.com/jp/blogs/news/aws-fargate/

これだけです。高いか安いか考察してみましょう。

EC2 インスタンスのオンデマンド価格との比較

us-east-1(N.Virginia)の価格 を見てみましょう。 Fargate価格 欄は上記のFargateの価格で同じvCPUとメモリを確保した場合の価格です。

インスタンスタイプ vCPU メモリ(GB) EC2価格 Fargate価格 倍率
m5.large 2 8 $0.096/h $0.2028/h x2.11
m4.large 2 8 $0.100/h $0.2028/h x2.03
m3.large 2 7.5 $0.133/h $0.1965/h x1.48
c5.large 2 4 $0.085/h $0.152/h x1.79
c4.large 2 3.75 $0.100/h $0.1488/h x1.49
c3.large 2 3.75 $0.105/h $0.1488/h x1.42
r4.large 2 15.25 $0.133/h $0.2949/h x2.22
r3.large 2 15 $0.166/h $0.2917/h x1.76
x1e.xlarge 4 122 $0.834/h $1.7518/h x2.10

m5シリーズであれば価格は性能と線形なので、たとえばm5.24xlargeでも倍率は同じx2.11になるためとりあえず比較しやすい上記に絞りました。

最新世代のm5やr4と比べて、おおむね2倍強の価格です。

Fargate高くない!? と思ったアナタ。ちょっと待ってください。

ECS Cluster AutoScaling構成との比較

多くの場合、ECSではそのメリットを享受するためにAutoScaling構成を取ります。たとえば、

  • MemoryReservation >= 60% で Scale-out
  • MemoryReservation < 30% で Scale-in

と設定するとしましょう1。この場合、実際に確保されるメモリ容量の割合は6割を超えません。
簡単のため、ECSでのリソース利用率はvCPU・メモリともに上記の中央値である45%程度であると仮定しましょう。
Fargateは 割り当てたvCPU・メモリに対する課金 ですので、それに対応するための計算です。

インスタンスタイプ vCPU45% メモリ45% EC2価格 Fargate価格 倍率
m5.large 0.9 3.6 $0.096/h $0.0913/h x0.95
m4.large 0.9 3.6 $0.100/h $0.0913/h x0.91
m3.large 0.9 3.375 $0.133/h $0.0884/h x0.66
c5.large 0.9 1.8 $0.085/h $0.0684/h x0.80
c4.large 0.9 1.6875 $0.100/h $0.0670/h x0.67
c3.large 0.9 1.6875 $0.105/h $0.0670/h x0.64
r4.large 0.9 6.8625 $0.133/h $0.1327/h x1.00
r3.large 0.9 6.75 $0.166/h $0.1313/h x0.79
x1e.xlarge 1.8 54.9 $0.834/h $0.7883/h x0.95

こうして見ると、 Fargateは非常に妥当な値付け であり、少なくとも高くないか やや安い 、と言えそうです。

さいごに

m5/r4シリーズのインスタンスが元々コスパ高いからSpotFleetで使おう

ECS/Fargateを使うときのコスト最適化については、こんな感じになりそうです。

  • SpotFleetを使っている場合にはそのままSpotFleetを使うのが良い
  • オンデマンドインスタンスでAutoScaling Groupを作っているなら、Fargateに乗り換えると良い

オンデマンド価格でECSを使っているならFargateに乗り換えない理由はありませんね!また、エンジニアの稼働を減らすことでコストメリットが出るなら、SpotFleetからFargateに乗り換えるのも十分アリなのでは、と思います2

ECS/Fargateを利用する場合には、他に ALBの利用料金 も発生します。コストを計算する場合にはそちらの考慮もお忘れなく!ALBの数が増えるとその分コストも増えますので、パスベースルーティングやホストベースルーティングを活用してALBの数を抑えるのも効果がありますよ。


  1. なぜこの数値を採用したかというと、実際使っているからです。Scale-outの閾値をあまりに高くしてしまうと、追加のTaskが起動することができずMemoryReservationが上昇できなくてScale-outして欲しいときに正しくScale-outしない、ということが起こりえます。 

  2. SpotFleetを使う場合、SpotFleetリクエストの期限などいくつか気にしないといけないことが増えます。 

続きを読む

[CloudWatch] 断続的なしきい値超過を捉えるアラームが設定しやすくなりました | Developers.IO

AWSのマネジメントコンソール「CloudWatch」の画面より「アラームの作成」を利用しました。 アラームの対象となるメトリックは、Application Load Balancer(ALB)のリクエストカウント(RequestCount) の「合計数」を利用しました。 続きを読む

AWS CloudFormationを使ってAWS Fargateの環境を作成してみる

本記事は個人の意見であり、所属する組織の見解とは関係ありません。

こちらはAWS Fargate Advent Calendar 2017の6日目の記事です。
AWS Fargateが発表されて、一週間ぐらい経ちました。新しいサービス、機能を色々試してみるのは楽しいですよね!

今日は、Fargateを触ってみて、もう少し本格的に取り組んでみたいと感じた方向けにAWS CloudFormationを使ってAWS Fargateの環境を作成する流れについて確認してみたいと思います。

AWS CloudFormationとは

Cloudformationでは、AWSリソースの環境構築を設定テンプレートを元に自動化する事ができます。ECSで利用する場合、TaskdefinisionやServiceの設定なども記述する事ができます。Containerのデプロイをより簡単に行える様になり各種自動化を行いやすくなるメリットもあります。

今回はFargateのAdvent Calendarへの投稿ですので、詳細については、次のWebinerの資料を確認してみてください。

CloudFormationテンプレート作成

作成方針

Cloudformationのテンプレートは記載の自由度が高く、色々な記述の仕方ができるのですが、今回は分かりやすさを重視して次の様な構成で分割したテンプレートを作成してみました。

  • VPC作成用テンプレート

    • Fargate用のVPCを作成し、VPCの設定を行うテンプレート
    • PublicSubnetやPrivateSubnet、ルートテーブルなどを作成していきます。
  • SecurityGroup作成用テンプレート

    • TaskやALBで利用するSecurityGroupを作成します。
  • ECSクラスターを作成するテンプレート

  • ELBを設定するテンプレート

  • TaskDefinitionテンプレート

    • ECS上で起動するContainerに関する設定を行います。
  • Serviceテンプレート

分割の仕方も様々ですので、各自のユースケースにあわせて、色々と試してみてください。個人的には、ライフサイクルが異なるリソースは別テンプレートにするが好きです。逆に、開発環境やデモ環境を素早く立ち上げたい場合は1つのテンプレートの中に全て記載してしまうのもいいですよね。

VPC作成用テンプレート

テンプレートの一部は次の様な形になります。(折りたたんでいます)

Resources:
    VPC:
        Type: AWS::EC2::VPC
        Properties:
            CidrBlock: !Ref VpcCIDR
            Tags:
                - Key: Name
                  Value: !Ref EnvironmentName

    InternetGateway:
        Type: AWS::EC2::InternetGateway
        Properties:
            Tags:
                - Key: Name
                  Value: !Ref EnvironmentName


  <省略>


Outputs:

    VPC:
        Description: A reference to the created VPC
        Value: !Ref VPC
        Export:
          Name: !Sub ${EnvironmentName}-VPC

ポイントは、作成したリソースに関する情報を別リソースからもアクセスできる様に
OutpusセクションでExport属性をつけている事です。Export属性で定義しているNameを利用して、
別テンプレートからも対象リソースに対する参照を行う事ができます。

SecurityGroup作成用テンプレート

ALB用、Container用のSecurityGroupを作成し、必要なPortを許可しています。

こちらも長いので、折りたたんでいます。

Description: >
    This template deploys a security-groups.

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

Resources:
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !ImportValue advent-calendar-2017-VPC
      GroupDescription: SecurityGroup for ALB
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-LoadBalancers


  ContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !ImportValue advent-calendar-2017-VPC
      GroupDescription: Security Group for Task
      SecurityGroupIngress:
        -
          SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
          IpProtocol: -1
        -
          CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80

      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-ContainerSecurityGroup

Outputs:
  LoadBalancerSecurityGroup:
    Description: A reference to the security group for load balancers
    Value: !Ref LoadBalancerSecurityGroup
    Export:
      Name: !Sub ${EnvironmentName}-LoadBalancerSecurityGroup

  ContainerSecurityGroup:
    Description: A reference to the security group for EC2 hosts
    Value: !Ref ContainerSecurityGroup
    Export:
      Name: !Sub ${EnvironmentName}-ContainerSecurityGroup

ECSクラスタ-を作成するテンプレート

非常にシンプルです。ただ、クラスタを定義して名前をつけるだけ。

Description: >
    This sample template deploys a ECS Cluster

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

Resources:
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${EnvironmentName}-cluster

Outputs:
  ECSCluster:
    Description: A referenc
    Value: !Ref ECSCluster
    Export:
      Name: !Sub ${EnvironmentName}-Cluster

ELBを設定するテンプレート

ALB、Listener、Targetgroupを作成しています。

Description: >
    This template deploys an Application Load Balancer.


Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017


Resources:
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Ref EnvironmentName
      Subnets:
        - !ImportValue advent-calendar-2017-PublicSubnet1
        - !ImportValue advent-calendar-2017-PublicSubnet2
      SecurityGroups:
        - !ImportValue advent-calendar-2017-LoadBalancerSecurityGroup
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
      Scheme: internet-facing

  LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref DefaultTargetGroup

  DefaultTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${EnvironmentName}-targetgroup
      VpcId: !ImportValue advent-calendar-2017-VPC
      Port: 80
      Protocol: HTTP
      TargetType: ip

Outputs:

  LoadBalancer:
    Description: A reference to the Application Load Balancer
    Value: !Ref LoadBalancer
    Export:
      Name: !Sub ${EnvironmentName}-Loadbalancer

  LoadBalancerUrl:
    Description: The URL of the ALB
    Value: !GetAtt LoadBalancer.DNSName

  Listener:
    Description: A reference to a port 80 listener
    Value: !Ref LoadBalancerListener
    Export:
      Name: !Sub ${EnvironmentName}-Listener

  DefaultTargetGroup:
    Value: !Ref DefaultTargetGroup
    Export:
      Name: !Sub ${EnvironmentName}-DefaultTargetGroup

TaskDefinition設定

ようやくFargateに関連する設定が出てきました。ここでは、RequiresCompatibilities属性にFARGATEを指定し、
NetworkMode属性にawsvpcを指定しています。また、CPU、メモリの設定はContainerDefinitionsの外側で設定します。
Container Definitionsにおけるmemory/cpuの指定はオプションです。加えて、各Taskがログを出力するためのCloudwatch Logsの設定もここで行なっています。

Description: >
    This sample deploys a task

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

Resources:
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/ecs/logs/${EnvironmentName}-groups'

  ECSTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Cpu: 256
      ExecutionRoleArn: arn:aws:iam::XXXXXXXXXXXX:role/ecsTaskExecutionRole
      Family: !Sub ${EnvironmentName}-task
      Memory: 512
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        -
          Name: nginx
          Image: nginx:latest
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: us-east-1
              awslogs-stream-prefix: ecs
          MemoryReservation: 512
          PortMappings:
            -
              HostPort: 80
              Protocol: tcp
              ContainerPort: 80

Outputs:
  LogGroup:
      Description: A reference to LogGroup
      Value: !Ref LogGroup

  ECSTask:
    Description: A reference to Task
    Value: !Ref ECSTask

Service設定

ここではFargate上でTaskを起動させるために、LaunchType属性にFARGATEを指定しています。ここでTaskNameに指定しているXXの数字はTaskのRevisionに該当します。Taskの更新とともにここの数字を変える必要があるという点がポイントです。

Description: >
    This sample deploys a service

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

  TaskName:
    Description: A task name
    Type: String
    Default: advent-calendar-2017-task:XX

Resources:
  Service:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !ImportValue advent-calendar-2017-Cluster
      DesiredCount: 2
      LaunchType: FARGATE
      LoadBalancers:
        -
          TargetGroupArn: !ImportValue advent-calendar-2017-DefaultTargetGroup
          ContainerPort: 80
          ContainerName: nginx
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            -
              !ImportValue advent-calendar-2017-ContainerSecurityGroup
          Subnets:
            -
              !ImportValue advent-calendar-2017-PrivateSubnet1
            -
              !ImportValue advent-calendar-2017-PrivateSubnet2
      ServiceName: !Sub ${EnvironmentName}-service
      TaskDefinition: !Ref TaskName
Outputs:
  Service:
      Description: A reference to the service
      Value: !Ref Service

Cloudformation Stackを作成する

これで、Fargate環境の作成準備が整いました。ここからは順番にStackを作成していきます。

$ aws cloudformation create-stack --stack-name advent-calendar-2017-vpc 
--template-body file://Fargate-vpc.yml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-security-group 
--template-body file://Fargate-security-groups.yaml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-load-balancer 
--template-body file://Fargate-load-balancers.yaml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-cluster 
--template-body file://Fargate-cluster.yml 
--region us-east-1


$ aws cloudformation create-stack --stack-name advent-calendar-2017-task 
--template-body file://Fargate-taskdefinition.yml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-service 
--template-body file://Fargate-service.yml 
--region us-east-1

作成した環境を確認する

Cloudformationでの環境構築が終わりました。正しく構築できているか、ALB経由でアクセスして確認してみてください。
作成したALBのFQDNは、マネージメントコンソール上のEC2の画面>ロードバランサにアクセスして確認できます。
それ以外にも今回の例では、CLIでの次の様なコマンドで確認する事ができます。(少し無理やりですが。。。)

$ aws cloudformation describe-stacks --stack-name advent-calendar-2017-load-balancer  
--region us-east-1 | jq '.Stacks[].Outputs[] | select(.OutputKey == "LoadBalancerUrl")'

{
  "Description": "The URL of the ALB",
  "OutputKey": "LoadBalancerUrl",
  "OutputValue": "advent-calendar-2017-844241308.us-east-1.elb.amazonaws.com"
}


####awscli単独でやるなら、次の様にも書く事ができます。

aws cloudformation describe-stacks --stack-name advent-calendar-2017-load-balancer  
--region us-east-1 
--query 'Stacks[].Outputs[?OutputKey == `LoadBalancerUrl`].OutputValue'

ECSクラスター

次のコマンドで存在が確認できます。

$ aws ecs list-clusters --region us-east-1
{
    "clusterArns": [
        "arn:aws:ecs:us-east-1:925496135215:cluster/advent-calendar-2017-cluster"
    ]
}

サービスの状態

作成したサービスの状態は次の様なコマンドで確認できます。

aws ecs describe-services --services <service name> 
--cluster <cluster name> --region us-east-1

例えば、デプロイしているサービスの状況を確認する際には以下の様なコマンドで状態を取得可能です。
次のコマンド結果には、runningCountが2であり、desiredCountの設定通りにTaskが起動している事が確認できます。


$ aws ecs describe-services --services advent-calendar-2017-service 
--cluster advent-calendar-2017-cluster 
--region us-east-1 | jq .[][].deployments
[
  {
    "status": "PRIMARY",
    "networkConfiguration": {
      "awsvpcConfiguration": {
        "subnets": [
          "subnet-2541e678",
          "subnet-9297e0f6"
        ],
        "securityGroups": [
          "sg-326f1047"
        ]
      }
    },
    "pendingCount": 0,
    "createdAt": 1512499161.953,
    "desiredCount": 2,
    "taskDefinition": "arn:aws:ecs:us-east-1:XXXXXXXXXXXX:task-definition/advent-calendar-2017-task:3",
    "updatedAt": 1512500281.269,
    "id": "ecs-svc/9223370524355613851",
    "runningCount": 2
  }
]

デプロイしたServiceを更新する

Cloudformationを利用して作成していますので、更新もCloudformation経由で行います。

テンプレートを更新する。

今回はDesiredCountを修正してみました。

$ diff Fargate-service-update.yml Fargate-service.yml
20c20
<       DesiredCount: 4
---
>       DesiredCount: 2

Stackを更新する

次の様なコマンドでStackの更新が可能です。

$ aws cloudformation update-stack --stack-name advent-calendar-2017-service 
--template-body file://Fargate-service-update.yml 
--region us-east-1

しばらく待った後に再びServiceの状態を確認するとDsierdCount通りにTaskの数が増えている事が確認できます。

$ aws ecs describe-services --services advent-calendar-2017-service 
--cluster advent-calendar-2017-cluster 
--region us-east-1 | jq .[][].deployments
[
  {
    "status": "PRIMARY",
    "networkConfiguration": {
      "awsvpcConfiguration": {
        "subnets": [
          "subnet-2541e678",
          "subnet-9297e0f6"
        ],
        "securityGroups": [
          "sg-326f1047"
        ]
      }
    },
    "pendingCount": 0,
    "createdAt": 1512499161.953,
    "desiredCount": 4,
    "taskDefinition": "arn:aws:ecs:us-east-1:925496135215:task-definition/advent-calendar-2017-task:3",
    "updatedAt": 1512538215.582,
    "id": "ecs-svc/9223370524355613851",
    "runningCount": 4
  }
]

Taskをアップデートする。

テンプレートを更新する。

Taskが利用するメモリの容量を修正してみました。

$ diff Fargate-taskdefinition-update.yml Fargate-taskdefinition.yml 
25c25
<       Memory: 1024
---
>       Memory: 512

Stackを更新する

次の様なコマンドでTask用のStackの更新をします。

$ aws cloudformation update-stack --stack-name advent-calendar-2017-task 
--template-body file://Fargate-taskdefinition-update.yml 
--region us-east-1

TaskのRevisionが変化していますので、Serviceでも新しいRevisionを利用する様に、テンプレートを修正して、Service用のStackを更新します。

$ aws cloudformation update-stack --stack-name advent-calendar-2017-service 
--template-body file://Fargate-service.yml 
--region us-east-1

再度、サービスの状態を確認して、起動しているTaskが更新されている事を確認してみてください。

まとめ

Cloudformationを利用したECS,Fargateの操作はいかがだったでしょうか。今回の記事を書く為に、新規でCloudformationテンプレートを作成したのですが、これまでのECSで利用していたテンプレートとの違いは僅かでした。FargateをきっかけにECSに興味を持って頂けた方の参考になればうれしいです。

続きを読む

クラウドベンダーの比較

はじめに

3大クラウドと呼ばれているAWS、GCP,Azureのリソースを比べてみました。
2017年11月時点の比較となります。

インフラ・サービスレベル

比較項目 AWS GCP Azure 備考
データセンター 各地で借りている すべて自前 各地で借りている(一部自前)
仮想化技術 Xen KVM Hyper-V
リージョン(国内) 1個所 1個所 2個所
リージョン(全国) 15個所 12個所 36個所
SLA 99.95 99.95 99.95 仮想サーバ

サービス面

比較項目 AWS GCP Azure 備考
仮想サーバ Amazon EC2 Google Compute Engine Azure Virtual Machines
仮想サーバ対応OS Amazon Linux,CentOS,RedHat,Windows Server,等 CentOS,RedHat,SLES,Windows Server,等 CentOS,RedHat,Windows Server,等
仮想サーバディスク SSD,HDD SSD,HDD SSD,HDD
仮想サーバスナップショット
仮想サーバオートスケール
コンテナ Amazon ECS Container Engine Container Service
RDB RDS Cloud SQL SQL Database
RDB冗長化
RDBリードレプリカ
RDB DB種別 Aurora,MySQL,MariaDB,Oracle,SQL Server,PostgreSQL MySQL,PostgreSQL SQL Server
NoSQL DynamoDB Cloud Datastore Cosmos DB
ビックデータ Redshift BigQuery App Service
メール Amazon SES
モニタリングツール CloudWatch Stackdriver Azure Monitor
ロードバランサー(L4) CLB Network load balancing Azure Load Barancer
ロードバランサー(L7) ALB HTTP load balancing Application Gateway
CDN Amazon CloudFront Google Cloud CDN Azure CDN
VPN Amazon VPC Google Cloud VPN VPN Gateway
DNS Route53 Google Cloud DNS Azure DNS
専用線 Direct connect Dedicated Interconnect Express Route

サポート面

比較項目 AWS GCP Azure 備考
ランク低 開発者 ($29/月 or 利用料の 3%/月) シルバー ($150/月) Standard($300/月)
ランク中 ビジネス($100/月 or 利用料 の10%/月) ゴールド($400/月) Professional Direct($1,000/月)
ランク高 エンタープライズ($15,000/月 or 利用料の10%) プラチナ(問合せ) Premier(問合せ)

費用面(リージョン日本)

比較項目 AWS GCP Azure 備考
課金単位
ディスカウント リザーブドインスタンス(前払い) 継続利用割引、利用確約の割引(前払い) エンタープライズ契約(前払い)
仮想サーバ(Type) t2.medium(2vCPU,4GB) n1-standard-2(2コア,7.5GB) A2 V2(2コア,4GB)
仮想サーバ(時) $0.0464(5.336円)※1 $0.0950(10.925円)※1 $0.15(17.25円)※1
仮想サーバ(月) $33.408(3841.92円)※1 $48.17(5539.55円)※1,※2 $108(12,420円)※1
インターネットへの転送量 $0.140/GB(10TBまで) $0.12/GB(1TBまで) $0.138/GB(5GB-10TB、5GBまでは無料)
ストレージ利用料 $0.025/GB (最初の50TB/月まで) ※S3 $0.016/GB 月 ※Regional Storage $0.2/GB(最初の50TB/月まで) ※BLOG Storage

※1 $1=115円換算
※2 継続利用割引含む

総括

 AWS、GCP,Azureでのサービス面では同じようなサービスを展開していることが判明。
 インフラ・サービスレベルでは、Azureがリージョン36個とTOPに。
世界的に見て幅を利かせているように思えた。
ただ、GCPはすべて自前のセンターでクラウドサービスを展開していることから、
新しいことをやるにも制約が低いように思えた。
私のイメージ的に一番シェアが高いAWSは、インフラ面ではGCP,Azureに劣っている結果となった。
 費用面では、Azureは割高。AWSが思ったよりも頑張っているように思えた。
 イメージ的にGCPは費用は安いと思っていたが、仮想サーバ比較だと、継続利用割引を使用してもAWSのほうが安い結果となった。
 ただ、費用に関しては、日々値下げ合戦が繰り広げられているので、今後のベンダーさんの努力に期待です。

最後に、費用面での算出に使用した見積もりツールです。
【AWS】http://calculator.s3.amazonaws.com/index.html
【GCP】https://cloud.google.com/products/calculator/?hl=ja
【Azure】https://azure.microsoft.com/ja-jp/pricing/calculator

続きを読む

AWSをさわってみたのでつまずいた点などをメモ

本記事はサムザップ Advent Calendar 2017の5日目の記事です。
昨日は@tomoriakiさんのゲームエンジニアが紙ヒコーキワークショップをやってみたでした

前提

・ インフラ歴半年
・ 業務では主にGCPを使用
・ GCPでできることをAWSで再現
・ 基本的にはterraformで構築
・ 大体検証期間は2週間くらい

目指したこと

既存のプロジェクト(GCP)と同等の開発環境構築

手順

※ 下記に記載する順番どおりに行ったわけではなく、記載上の問題で番号を振っています。

1. VPC構築

まずは何はともあれVPCとサブネット、サブネット配下にインスタンスを1台作成しました。

つまずき1
インスタンスにsshアクセスできない!
※ FWの設定(awsではセキュリティーグループ(sg)っていうのかな)はingress port:22許可したのに

原因と解決
インターネットゲートウェイ(igw)ってものをサブネットにアタッチしないとprivateなサブネットになってしまい、アクセス出来ないとのこと。


2. VPCにpriveteとpublicのサブネット構築

1.でigwを置いたけどそもそも接続出来ない領域分けたいよね、ということでサブネットを2つ作ってigwをアタッチするpublicサブネットとアタッチしないpriveteサブネットを作成
それぞれにインスタンスを1台ずつ置きpriveteインスタンスにはpublicインスタンスからinternal接続する

つまずき2
privateサブネットに置いたインスタンスでyum updateできない

原因と解決
privateサブネットだと外へのアクセスもできないらしい
NATゲートウェイ(ngw)というものをpublicサブネットに配置して、それ経由でpriveteサブネットのインスタンスは外へのアクセスをできるようにした。

※ 最初は名前的にEgress Only インターネットゲートウェイだと思ったけど、こちらはipv6らしい

TIPS
S3への接続はなにもしないと外部経由になってしまうらしい
エンドポイント(ep)というものをVPCに配置するとinternalで接続できるのでVPCを作ったらとりあえず、epも配置しよう!!


3. プロジェクトと開発環境の構成

1,2でVPCはだいたい出来たけど、
・ プロジェクトが違う場合
・ 環境(development, staging, productionなど)が違う場合
どのようにネットワークを分けて構築すればいいかを考えた。

参考
下記サイトでアカウントを分ける場合とVPCを分ける場合を分かりやすくまとめている
こちらを参考に本番と開発を分け(パターン4)、開発はVPCで分ける(パターン2-1)ように考慮した
https://dev.classmethod.jp/cloud/aws/account-and-vpc-dividing-pattern/


4. LBの作成

TCP, HTTP, HTTPSロードバランサの作成

つまずき3
terraformのmoduleロードバランサ関連多くてどれ使えばいいか分からん

原因と解決

terraform module名 awsでの呼び方 備考
elb classic ロードバランサ
alb application ロードバランサ 非推奨
(今後のバージョンアップで削除予定)
lb application ロードバランサ
network ロードバランサ
基本的にはこちらを使用する

TIPS
application LBはマルチAZ構成を強制されているのでゾーンの違うpublicなサブネットを2つ用意する必要がある。


5. インスタンスのホスト名解決

gcpではinternalな通信にホスト名を使用できる。

つまずき4
awsだとホスト名がipでつけられそもそも分かりづらいので、ホスト名を設定しつつ名前解決をしたい。

原因と解決
インスタンス登録時にtags:Nameにインスタンス名を設定し、そのインスタンス名とprivateIPをroute53でレコード登録する

サーバ自体はterraformのprovisionerで下記を実行

sudo sed -i -e "s/localhost.localdomain/[ホスト名]/g" /etc/sysconfig/network
sudo hostname [ホスト名]

まとめ

・ awsはgcpに比べてやれることが多い分、使う側がネットワークの知識とか結構求められるな!という印象を受けました。terraformのコード量もgcpの2倍くらいになった気がする。

・ マルチAZ構成を考える際は結構パフォーマンスに注意する必要がありそう。別ゾーンへのアクセスはパフォーマンスが落ちるという検証結果が結構あるので、問題ないように構成を考えないといけない

・ あとはざっくりgcpはブラックリスト、awsはホワイトリストなセキュリティーな印象

terraformのコード

https://github.com/bayguh/terraform_aws


明日は@Gaku_Ishiiさんの記事です。

続きを読む

VPC内に置いたElasticsearch ServiceにEC2のプロキシ経由でアクセスする

先日のアップデートでElasticsearch ServiceをVPC内で使えるようになりました。
https://aws.amazon.com/jp/blogs/news/amazon-elasticsearch-service-now-supports-vpc/

これまではパブリックなエンドポイントしか使えず、VPC内からアクセスするにはパブリックサブネットからの通信が必要でした。
VPC内に配置できれば、通信も全部VPC内で完結しますし、よりセキュアに使うことができるようになりますね。

さて、VPC内に配置したときに起きる問題の一つが、 直接ローカルからKibanaを見ることができない という問題です。
パブリックサブネットに配置してセキュリティグループで接続許可すれば行けるだろう、と最初は思ったんですが、どうやら無理みたいです。
※うまく行った、という方がいたらご教示ください…。

これを解決するためにどうするかについてはいろいろ調べてみました。
1. ALBを使う。
こちらを参考
https://dev.classmethod.jp/cloud/aws/make-public-kibana-amazon-es/
メリットは管理するサーバが増えないこと。
デメリットは、Elasticsearch ServiceのプライベートIPは可変なのに対し、ALBはターゲットにIPかEC2インスタンスしか設定できず、ターゲットのIPを定期更新するなどしなければならないこと。
1. EC2にKibanaをインストールして、Easticsearch Serviceを参照するようにする。
こちらを参考
https://dev.classmethod.jp/cloud/aws/make-public-kibana-amazon-es2/

メリットは、ドメイン指定が可能なのでElasticsearch ServiceのIPが変わっても大丈夫なことなど。 デメリットは、Kibanaの部分がEC2依存になること。
1. Windowsのインスタンスを立ててそこにリモートデスクトップでアクセスし、Windowsインスタンス内のブラウザからKibanaにアクセスする。
読んで字のごとくです。
メリットはやることが単純明快なことかな?
デメリットは大抵のケースで余計なEC2インスタンスを増やさざるを得ないこと。
1. EC2をプロキシサーバにする。(今回の方法)
EC2にKibanaを入れるパターンに近いですが、KibanaもElasticsearch Serviceのものを使用するので、Kibanaの可用性をElasticsearch Service(つまりAmazon側)に依存させたままにできるのがメリットです。
デメリットはこちらもアクセスがEC2依存になること。

今回は調べてもVPC内に配置したElasticsearch Serviceを参照するために使用したという例が見つからなかった、プロキシサーバを使う例について書いてみます。
※別の目的でプロキシサーバ経由にする例はたくさんありましたが、いずれも今回のアップデート以前の情報でした。
今回の例ではnginxを使用しますが、別にApacheでもなんでもいいと思います。

方法

パブリックサブネットにEC2インスタンスを用意する。

特に何も考えずにAmazon Linuxでよいと思います。
インスタンスタイプはとりあえずt2.microでもいいですが、
使ってるうちに負荷がしんどいなと思ったら適宜変更しましょう。
当然のことながら、SSHに加えhttpでアクセスできるようにセキュリティグループを設定します。
場合によっては踏み台サーバと共用とかでも大丈夫かなと。
※今回の例ではhttpを使用し、httpsではやらないようにします。
httpsを使用する例はこちら。(今回の設定もこちらを参考にしています。)
http://inokara.hateblo.jp/entry/2016/10/22/123538

用意したEC2からElasticsearch Serviceクラスタにアクセスできるようにする。

セキュリティグループの設定およびアクセスポリシーを適切に設定します。
セキュリティグループだけにしてもいいですし、リソースベース、IPベース、IAMユーザ・ロールベース等ポリシー設定できるのでその時の要件に沿って適切に設定すればOK。
http://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-createupdatedomains.html#es-createdomain-configure-access-policies
余談ですが、Elasticserch Serviceの設定変更には意外と時間がかかるので、なるべく設定変更が少ないほうが気持ちが楽だと思います。

EC2インスタンスにnginxをインストールする。

深く考えずにyumでOKです。

sudo yum -y install nginx

/etc/nginx/nginx.confを編集する。

50行目付近に

nginx.confの一部
        include /etc/nginx/default.d/*.conf;
        location / {
        }

みたいに空で設定されているところがあるので、下記のようにします。
※抜粋、略記しています。

nginx.confの一部
        include /etc/nginx/default.d/*.conf;
        location / {
            proxy_pass https://xxxxxxxxxxxxxx.amazonaws.com/;
        }

といった感じに、proxy_pathでVPCエンドポイントを指定します。
httpsじゃなくてもいいみたいですが、httpsで問題なくアクセスできます。

nginxを起動する。

nginxのサービスを起動します。ついでに自動起動もONにしておきましょう。

sudo service nginx start
sudo chkconfig nginx on

これで
http://{{設定したインスタンスのパブリックIP}}/_plugin/kibana
にアクセスしてやれば、EC2を経由して該当のElasticsearch ClusterのKibanaにアクセスできます。

課題とかおまけとか

後ろに_plugin/kibanaをつけないでアクセスするとElasticsearchにもアクセスできちゃうので、それを良しとするかどうかは要検討です。
逆にローカルから手軽にデータ投入できたりもするわけですが…。どこまで許容するかですね。
そこを気にするのであれば、nginxの設定とかでディレクトリごとにアクセス制御するとかが必要かもしれません。

あと、Elasticsearch Serviceを配置したVPCにVPN接続している場合はこんなことしなくてもVPNエンドポイントを指定して直接参照できる気がするので、試した方いたらご教示くださると嬉しいです。

ALBでElasticsearch Serviceのインスタンスを指定できるようになればいいんですけどねえ。

続きを読む

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などとも比べながら、本環境をアップデートしていければよいなと思います。

続きを読む