CodedeployのLifeCycleに関するメモ

CodedeployのLifeCycleに関する雑な理解

大きくアプリケーション周りのライフサイクル(7つ)とロードバランサーに関係するライフサイクル(6つ)がある。初回デプロイ時にはDownloadBundleされる前のApplicationStopはrunscriptがないので実行されない。

Application周りのライフサイクル(Application止めて、正常稼働確認まで)

  • ApplicationStop
  • DownloadBundle
  • BeforeInstall
  • Install
  • AfterInstall
  • ApplicationStart
  • ValidateService

LB周りのライフサイクル

  • BeforeBlockTraffic
  • BlockTraffic
  • AfterBlockTraffic
  • BeforeAllowTraffic
  • AllowTraffic
  • AfterAllowTraffic

runscriptの実行可否一覧

IP:InPlaceDeployment
BG:Blue/GreenDeployment
BGR:Blue/GreenDeployment Rollback
CLB:ClassicELB

◯:runscriptは実行される
△:runscriptは場合によって実行される(2回目以降のデプロイから)
×:runscriptは実行されない
太字:runscriptは実行されない。codedeploy占有。

LifeCycleEvent IP(LB無し) IP+CLB IP+ALB BG(Blue) BG(Green) BGR(Blue) BGR(Green)
1.ApplicationStop × × ×
2.DownloadBundle × × × × × × ×
3.BeforeInstall × × ×
4.Install × × × × × × ×
5.AfterInstall × × ×
6.ApplicationStart × × ×
7.ValidateService × × ×
8.BeforeBlockTraffic × × × ×
9.BlockTraffic × × × × × × ×
10.AfterBlockTraffic × × × ×
11.BeforeAllowTraffic × × × ×
12.AllowTraffic × × × × × × ×
13.AfterAllowTraffic × × × ×

runscript内で使える環境変数

個人的にDEPLOYMENT_GROUP_NAMEくらいしか使わない。

  • APPLICATION_NAME
  • DEPLOYMENT_ID
  • DEPLOYMENT_GROUP_NAME
  • DEPLOYMENT_GROUP_ID
  • LIFECYCLE_EVENT

個人的Tips

  • runscriptの名前にイベントの順序ごとに番号つける。1_application_stop.sh等にしておくと順序悩まない。
  • とりあえずEC2のUSERDATAやAutoScalingのLaunchConfigurationに以下のコード入れて置くと楽。
#!/bin/bash
REGION=$(curl 169.254.169.254/latest/meta-data/placement/availability-zone/ | sed 's/[a-z]$//')
yum install -y aws-cli
cd /home/ec2-user/
aws s3 cp s3://aws-codedeploy-${REGION}/latest/install . --region ${REGION}
chmod +x ./install
./install auto

参考

Lifecycle Event Hook Availability

続きを読む

【AWS】Elastic Beastalk

はじめに

タダです。
AWS認定試験勉強のためにElastic Beanstalkのドキュメントを読んだ自分用メモになります。
※違う内容書いているなどありましたらご指摘いただけると幸いです。
※随時アップデートがあれば更新していきます。

サービスの概要

  • Elastic Beanstalkは、ソースコードをアップロードするだけで、ソースコードを実行する環境のプロビジョニング、ロードバランサー、スケーリング、モニタリングなどの細かい作業はサービスを管理するサービス

    • サポートするのは、PHP、Java、Python、Ruby、Node.js、Docker
  • 構成できるのは、ウェブサーバー環境とワーカー環境
    • ウェブサーバー環境は、ELB + AutoScalingでスケーラブルな環境を構成し、環境毎にDNS名を付与する
    • ワーカー環境は、SQS + AutoScalingでスケーラブルなバッチ処理基盤を構成する
      • Sqsdはワーカーホスト内で動作するデーモン

        • 200 OKならSQSのメッセージを削除
        • 200 OK以外ならVisibilityTimeout(SQSの設定)後にSQSからメッセージが取得可能(リトライ)
        • 応答なしならInactivity Timeout(Elastic Beanstalkの設定)後にSQSからメッセージが取得可能(リトライ)
      • 定期的なタスク実行も可能(cron.yamlで定義)

特徴

  • デプロイオプションを使って、簡単に新しいアプリケーションバージョンを実行している環境にデプロイできる
  • CPU平均使用率、リクエスト数、平均レイテンシーなどCloudWatchモニタリングメトリクスにアクセスできる
  • アプリケーションの状態が変化したり、アプリケーションサーバが追加または削除されたりした際にはSNSをつかって通知が行われる
  • アプリケーションサーバにログインせずにサーバのログファイルのアクセスできる
    • S3に保管される
  • AMI、オペレーティングシステム、言語やフレームワーク、およびアプリケーションサーバーまたはプロキシサーバーなどアプリケーションを実行する基盤となるプラットフォームに対する定期的な自動更新を有効にできる
  • アプリケーションサーバ設定(JVM設定など)を調整して環境変数を渡す

サービスの構成要素

  • アプリケーション : トップレベルの論理単位

    • バージョン、環境、環境設定が含まれている入れ物
  • バージョン : デプロイ可能なコード
    • S3上でのバージョン管理
    • 異なる環境に異なるバージョンをデプロイ可能
  • 環境:Webサーバ、ワーカーに応じて構築されるインフラ環境
    • バージョン(ソースコード)をデプロイ
  • 環境設定:その環境に関連するリソースの動作を定義するパラメーター
    • EC2インスタンスタイプ、AutoScalingの設定など
       
      ## 環境のタイプ
  • ロードバランシング、AutoScaling環境
    • 高い可用性と伸縮自在性を兼ね備えた構成
    • ウェブサーバー環境:ELB + AutoScaling
    • ワーカー環境:SQS + AutoScaling
  • シングルインスタンス環境
    • EC21台構成(AutoScalingでmax,minが1に設定されている)
    • 開発環境などの構築のために低コストで構築可能

環境設定

環境に関連するリソースの動作を定義する設定パラメーター

  • 直接設定

    • マネジメントコンソールまたはElastic Beastalk CLI(eb createオプションで実施)
  • 保存済み設定
    • 環境の作成中または実行中の環境に適用できる設定をS3に保存して流用できる(ステージングで使った設定を本番に適用)
  • デフォルト値
  • .ebextensions
    • ウェブ環境のソースコードに.ebextensionsを追加することで環境を設定し、環境に含まれるAWSリソースをカスタマイズできる(JVM設定など)
    • 環境で使用しているリソースのカスタマイズが可能
    • 環境に対する様々な操作を自動化、集約可能
~/workspace/my-app/
|-- .ebextensions
    |-- environmentvariables.config
    |-- healthchekckurl.config
|-- .elasticbeanstalk
    |-- config.yml
|-- index.php

.ebextensionsで実行可能な操作

  • packages:yum,rpmでのパッケージのインストール
  • sources:外部からのアーカイブをダウンロードした場所に展開する
  • users/groups:任意のユーザー/グループを作成
  • commands:デプロイ処理前に実行すべきコマンドやスクリプトを指定
  • container_commands:新バージョンの展開後に実行すべきコマンドやスクリプトを指定
  • option_settings:環境変数の設定
  • Resouteces:追加のリソースを定義(SQSのキュー、DynamoDBテーブル、CloudWatchのアラーム)

.ebextensions Tips

  • セクションごとにファイルを分割する
  • インストールするパッケージのバージョンを明記
    • インスタンスによって異なるバージョンになることを防止
  • カスタムAMIとのトレードオフを検討

Dockerサポート

  • Dockerを構成する場合、Single Container(EC2の中にDockerを実行)またはMulti Container(ECS)を利用可能
  • DockerをElastic Beanstalkでデプロイする場合、Dockerrun.aws.jsonで定義して実行する
{
  "AWSEBDockerrunVersion": "1",
  "Image": {
    "Name": "janedoe/image",
    "Update": "true"
  },
  "Ports": [
    {
      "ContainerPort": "1234"
    }
  ],
  "Volumes": [
    {
      "HostDirectory": "/var/app/mydb",
      "ContainerDirectory": "/etc/mysql"
    }
  ],
  "Logging": "/var/log/nginx"
}

デプロイ形式

Elastic Beastalkのデプロイ形式を以下にまとめる

メソッド 概要
All at once 同時にすべてのインスタンスに新しいバージョンをデプロイする。環境内のすべてのインスタンスは、デプロイが実行される間、短時間だがサービス停止状態になる。
Rolling バッチに新しいバージョンをデプロイする。デプロイフェーズ中、バッチはサービス停止状態になり、バッチのインスタンスによる環境容量への負荷を低減する。
Rolling with additional batch バッチに新しいバージョンをデプロイするが、デプロイ中に総容量を維持するため、インスタンスの新しいバッチをまず起動する
Immutable 変更不可能な更新を実行し、新しいバージョンをインスタンスの新しいグループにデプロイする。

モニタリング

  • 基本(ベーシック)ヘルスレポート

    • 環境のヘルスステータス
    • ELBのヘルスチェック
    • CloudWatchメトリクス
  • 拡張ヘルスレポート
    • OSレベルのメトリクス
    • アプリケーションレベルのメトリクス

拡張ヘルスレポートの主なメトリクス

  • EnvironmnetHealth
  • インスタンスの状態
  • リクエスト総数及び各レスポンスコード毎の数
  • x%の完了のかかった平均時間
  • LoadAverage1min: 1分間のLoad値の平均値
  • RootFilesystemUtil: 使用ディスク容量の割合
  • CPU使用状況詳細

他のAWSサービスとの統合

  • CloudFront

    • Elastic Beanstalkでデプロイしたら、CloudFrontから配信する
  • DynamoDB
    • Elastic BeastalkでDynamoDBのテーブルを作成し、データを書き込む
  • Elasticache
    • セキュリティグループでElastic Beastalkでアクセスできるように設定する
  • RDS
    • セキュリティを高めるために、接続情報をS3に保存し、デプロイの間にデータを取得するようにElastic Beastalkを設定する
    • 設定ファイル(ebextensions)を使用して環境内のインスタンスを設定し、アプリをデプロイする時にS3からファイルを安全に取得できる
  • S3
    • S3バケットは、アプリケーションのバージョン、ログ、その他のサポートファイルファイルを保存する

メンテナンスウィンドウ

メンテナンスウィンドウは、管理プラットフォームの更新が有効化されており、プラットフォームの新バージョンが公開されている場合にElastic Beanstalkでプラットフォームの更新が開始される毎週2時間の時間帯が割り当てられる

EB CLIについて

コマンドラインインターフェースとして、EB CLIがある

  • eb create

    • 初期環境を作成する
  • eb status
    • 環境のステータスを確認する
  • eb health
    • 環境内のインスタンスのヘルス情報とその環境全体の状態を表示する
  • eb events
    • 環境のイベントのリストを確認する
  • eb logs
    • 環境のインスタンスからログを取得する
  • eb open
    • ブラウザでウェブサイトのユーザー環境を開く
  • eb deploy
    • デプロイする
  • eb config
    • 実行する環境に利用可能な設定オプションを確認する
  • eb terminate
    • 環境のターミネート

参考

更新日時

  • 2017/05/01 初回投稿
  • 2017/05/05 環境設定の項目を更新、Dockerサポートを追加

続きを読む

【AWS】ELB(CLB)

はじめに

タダです。
AWS認定資格の試験勉強のためにELBの情報をまとめた自分用メモになります。
あくまで個人用のメモになるので、その点はご了承ください。
※違う内容書いているなどありましたらご指摘いただけると幸いです。
※本記事では、Classic Load Balancerの情報をまとめていきます。
※随時アップデートがあれば更新していきます。

サービス概要

ELBは2つのタイプに分けられる

  • Classic Load Balancer

    • アプリまたはネットワークレベルのいずれかの情報に基づいてルーティングする
    • 複数のEC2インスタンス間でシンプルなトラフィックのルーティングに適する
  • Application Load Balancer
    • リクエストパスまたはホストベースでルーティングする
    • 複数のサービスにルーティングしたり、同じEC2インスタンスの複数のポート間で負荷分散するのに適する

特徴

  • スケーラブル

    • ELB自体も負荷に応じてスケールアウト
  • 可用性向上
    • 複数AZへの振り分け、インスタンスのヘルスチェック
  • マネージドサービス
  • ルーティング方式
    • 基本はラウンドロビン
  • コネクションタイムアウト
    • クライアントからの無通信状態が続くと接続を自動で切断する(デフォルト60秒)
  • ELB自体のスケーリング
    • じんわりとしたトラフィックの増加には対応できる

      • 逆に急激なトラフィックの増加にはスケーリング対応できないため、事前にPre-Warming(暖機運転)の申請をAWSに行う
  • スティッキーセッション
    • 同じユーザーから来たリクエストをすべて同じEC2に送信
    • アプリケーションでのセッション情報、一時ファイルなどをEC2が保持する構成の場合に必要
    • HTTP/HTTPSでのみ利用可能
    • EC2の増減を柔軟に管理できるようセッション情報は別のDBやキャッシュサーバに保持させるのが好ましい
  • Connection Draining
    • EC2インスタンスとELB間の登録解除、ヘルスチェック失敗した時にルーティングを中止して、処理中のリクエスト終わるまで一定期間まつ
  • X-Forwarded-For
    • HTTPまたはHTTPSロードバランサーを使用する場合に、クライアントの IP アドレスを識別するのに役立つ
  • Proxy Protocol
    • ELBはProxy Protocolバージョン1をサポートしている
    • Proxy Protocolヘッダーはバックエンド接続用にTCPを使う場合、クライアントのIPアドレスを識別するのに役立つ
    • 有効化する場合の前提条件は以下の通り
      • ELBの背後にプロキシサーバがいないこと
      • インスタンスでProxy Protocol情報を処理できるか確認する
      • リスナー設定でProxy Protocloがサポートされているかを確認する
  • 他のサービスとの連携機能
    • AutoScaling
    • Route53
    • CloudFormation
    • OpsWorks
    • Elastic Beanstalk
    • CloudWatch
    • EC2
    • S3など

サポートプロトコル

Classic Load Balancerのサポートプロトコルは、以下の通り

  • HTTP
  • HTTPS
  • SSL(セキュアTCP)
  • TCP

ロードバランサーのタイプ

ロードバランサーのタイプとして、外向けロードバランサーと内向けロードバランサーがある

  • 外向けロードバランサー : インターネットからアクセスできるELB
  • 内向けロードバランサー : VPC内やオンプレ環境からのみアクセスできるELB

モニタリング

ELBの監視を行う場合は以下の方法がある

  • CloudWatchメトリクス
  • ELBのアクセスログ
    • ELBへのリクエストの詳細情報をキャプチャしているので、確認する
  • CloudTrailログ

トラブルシューティング

よくELBのトラブルシューティングでHTTPステータスから設定不足を確認することがあるので、ドキュメントの情報をまとめる

  • HTTP 400: BAD_REQUEST

    • 原因:クライアントが誤ったリクエストをしてしまったため発生
    • 解決策:直接インスタンスに接続し、クライアントリクエストの詳細をキャプチャする
  • HTTP 405: METHOD_NOT_ALLOWED
    • 原因:リクエストヘッダー内のメソッドの長さが127文字を超える
    • 解決策:メソッドの長さを確認する
  • HTTP 408: Request Timeout
    • 原因:指定されたコンテンツのサイズが実際に創始されたコンテンツサイズと一致しないや、クライアントとの接続が閉じているため発生
    • 解決策:リクエストを生成しているコードを調べ、リクエストをより詳細に調べることのできる登録済みインスタンスに直接送信する、レスポンスが送信される前にクライアントが接続を閉じないようにする
  • HTTP 502: Bad Gateway
    • 原因:インスタンスからの応答の形式が適切でないか、ロードバランサーに問題がある
    • 解決策:インスタンスから送信された応答が HTTP 仕様に準拠していることを確認する
  • HTTP 503: Service Unavailable
    • 原因:ロードバランサーにリクエストを処理する能力が不足または、登録されたインスタンス(正常なインスタンス)が存在しない
    • 解決方法:前者の場合は一時的な問題のため、一旦放置氏それでも解決しない場合はAmazon Web Servicesサポートへ問い合わせ
    • 後者の場合、正常なインスタンスを登録する
  • HTTP 504: Gateway Timeout
    • 原因:アプリケーションの応答が、設定されているアイドルタイムアウトよりも長くかかっているまたは、録されたインスタンスがELBへの接続を終了させている
    • 解決策:前者は、ELB側のアイドル接続のタイムアウト設定を伸ばす
    • 後者の場合は、MWのKeepAlive設定を有効にしてELBのタイムアウト設定よりも大きい値を定義する

参考

更新日時

2017/05/01 初回投稿

続きを読む

【AWS】OpsWorks スタック

はじめに

タダです。
AWS認定資格の試験勉強のためにCloudFormationの情報をまとめた自分用メモになります。
あくまで個人用のメモになるので、その点はご了承ください。
※違う内容書いているなどありましたらご指摘いただけると幸いです。
※随時アップデートがあれば更新していきます。
※本記事では、OpsWorks スタック中心のまとめになります。

サービス概要

  • OpsWorksはChefを使ったAWSリソース(EC2、ELB、RDSなど)やEC2上のアプリケーションの設定・管理を行うためのサービス
  • Chefサーバーは不要で、OpsWorksスタックは、インスタンスのヘルスチェックをモニタリングし、自動復旧および Auto Scaling を使用して、必要な場合に新しいインスタンスをプロビジョニングする
  • Chefのcookbookレシピを使って、インフラの管理を自動化、アプリケーションの継続的なインストール、構成、管理、デプロイ、スケールが可能なのがChef Automate
    • Chefサーバありきだが、管理はAWS

OpsWorksエージェント

  • OpsWorksスタックで必要

    • OpsWorksの一連のコマンドを取得し、エージェントがChef Clientのローカルモードでレシピを実行

利用メリット

アプリケーションのモデル化とサポート
管理作業を自動化

CloudFormation/Elastic Beastalkとの違い

  • OpsWorksは、DevOps環境を提供することを目的におき、デプロイ、モニタリング、自動スケーリング、自動化の主要なアクティビティに対して統一された設定管理を行う
  • CloudFormationはAWSリソースのプロビジョニングと管理をJSON/YAMLで行うことを目的とする
  • Elastic BeastalkはJava、.NET、PHP、Node.js、Python、Ruby、Go、Docker で開発されたウェブアプリケーションとウェブサービスをデプロイおよびスケーリングする

制限

デフォルトでは、最大40スタックを作成できる
最大40のレイヤー、最大40のインスタンス、最大40のアプリケーションを保持できる
 

専門用語

  • スタック

    • OpsWorksの管理対象をまとめたコンポーネントで、属する全員インスタンスの構成を管理する
  • レイヤー
    • 1つ以上の Layer を追加することにより、スタックのコンポーネントを定義する
    • レイヤーは、アプリケーションへのサービス提供やデータベースサーバーのホストのような特定の目的を果たす一連のEC2インスタンスを表す
  • インスタンス
    • インスタンスのスケーリングタイプは3つある

      • 24/7インスタンス(常時稼働)
      • load-basedインスタンス
        • 負荷に対してサーバを起動することができる
        • メトリックスの閾値は次のものを定義し、インスタンスの増減をコントールできる
          • [Average CPU] – Layer の平均 CPU 使用率 (合計に対する割合)
          • [Average memory] – Layer の平均メモリ使用率 (合計に対する割合)
          • [Average load] – Layer の平均負荷
      • time-basedインスタンス
        • 時間ベースのスケーリングにより指定したスケジュールでインスタンスを起動、停止できる
        • 特定の時間または特定の曜日にレイヤーによりオンラインにされるインスタンス数を制御できる
  • App
    • アプリケーショサーバにデプロイするアプリケーション
  • レシピ
    • Chefレシピを実行して、アプリケーションの設定、アプリケーションのデプロイ、スクリプトの実行などを行う

スタックコマンド

スタック全体の構成を変更・管理するためのコマンドで以下のようなものがある
実行方法はAWSマネジメントコンソールやCLI、SDKなど

  • Update Custom Cookbook

    • リポジトリにある更新されたCookbookをそれぞれのインスタンスに展開する
  • Execute Recipes
    • 指定したレシピを指定したインスタンス上で実行する
  • Setup
    • Setupのレシピを実行する
  • Configure
    • Configureのレシピを実行する
  • Upgrade Operationg System
    • (Linuxのみ)Amazon Linuxを最新バージョンにアップグレードする

ライフサイクル

OpsWorksのインスタンスで自動的に行う処理をさす

  • Setup : 新しいインスタンスが正常にブートした後に発生する

    • ex: ロードバランサーをインストール、アプリケーションサーバをインストール、データベースをインストール
  • Configure : インスタンスがオンライン状態に移行したときとオンライン状態から移行した時にスタックのすべてのインスタンスで発生する
    • ex: アプリケーションサーバのIPをアップデート、DB接続先をアップデートして再起動、アプリケーションサーバのIPのACLをアップデート
  • Deploy : ユーザーがアプリケーションをデプロイする時に発生する
    • ex:アプリケーションコードをアップデートして再起動
  • Undeploy : アプリケーションを削除する時に発生する
    • ex:アプリケーションを削除して再起動
  • Shutdown : インスタンスの終了
    • コネクションをDrainする、ログを保存、スナップショットの保存

Opsworksと他サービスとの連携

モニタリング

スタックは次の方法で監視可能

  • CloudWatchによるスタックのインスタンスごとに詳細モニタリング
  • CloudTrailによるAPI監視
  • CloudWatch Logsでスタックのシステム・アプリケーション・カスタムログを監視する

セキュリティおよびアクセス許可

  • VPC内に展開可能
  • AWSリソースにアクセスするためのACLやインスタンスへの接続管理はIAM(ユーザー、許可、ロール)で行う

参考

更新日時

  • 2017/05/01 初回投稿

続きを読む

【AWS】CloudFormation

はじめに

タダです。

AWS認定資格の試験勉強のためにCloudFormationの情報をまとめた自分用メモになります。
あくまで個人用のメモになるので、その点はご了承ください。
※違う内容書いているなどありましたらご指摘いただけると幸いです。
※随時アップデートがあれば更新していきます。

CloudFormationとは

AWSリソースを作成したり、管理するのが役立つツールです。JSONやYAML形式でリソース定義できる

CloudFormationを使う主なメリットは以下の通り

  • AWSリソース管理が簡略化
  • インフラリソースを素早く展開/複製
  • インフラリソースの制御や変更も簡単

Elatic Beanstalkとの違い

  • Elatic Beanstalkはアプリを簡単にデプロイ及び実行できる環境を提供する

    • アプリのライフサイクルを管理するためのサービス
  • CloudFormationはAWSリソースの作成をサポートする
    • Elatic Beanstalkもサポートしている

CloudFormationの概念

  • テンプレート

    • YAMLまたはJSON形式のテキストファイルでAWSリソース作成のための設計図

      • 5種類の要素で構成される

        • テンプレートパラメータのオプションリスト
        • 出力値のオプションリスト
        • 静的な設定値を見るのに使用するデータテーブルのオプションリスト
        • AWS リソースとそれらの設定値のリスト
        • テンプレートファイルフォーマットのバージョン番号
    • 具体的には以下のようなものがテンプレート例
{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "A sample template",
  "Resources" : {
    "MyEC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "ImageId" : "ami-2f726546",
        "InstanceType" : "t1.micro",
        "KeyName" : "testkey",
        "BlockDeviceMappings" : [
          {
            "DeviceName" : "/dev/sdm",
            "Ebs" : {
              "VolumeType" : "io1",
              "Iops" : "200",
              "DeleteOnTermination" : "false",
              "VolumeSize" : "20"
            }
          }
        ]
      }
    }
  }
}
  • スタック

    • CloudFormationの関連リソースは、スタックと呼ばれます。これを作成、変更、更新することでリソース制御を行う
    • スタックの更新には2つの方式がある
      • 直接更新(in place)と変更セットの作成(blue-green)と実行
  • 変更セット
    • スタックの更新をおこなう時の概要が変更セットと呼び、影響度を確認するためのスタック

変更セットについて

変更セットの作成

更新元のスタックをもとに変更セットを作成します
Cloudformation -> [Actions],[Create Change Set For Current Stack]を選択
その他のパラメーターは、スタックを作成するのと同じ操作で行う
CLIだと、以下のように実行する

aws cloudformation create-change-set --stack-name arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/1a2345b6-0000-00a0-a123-00abc0abc000
--change-set-name SampleChangeSet --use-previous-template --parameters ParameterKey="InstanceType",UsePreviousValue=true ParameterKey="KeyPairName",UsePreviousValue=true ParameterKey="Purpose",ParameterValue="production"

変更セットの実行

変更セットに記述された変更をスタックに加えるには、変更セットを実行する
※変更セットを実行するとスタックに関連付けられた変更セットは削除される点は注意。
CLIだと、以下のように実行する

aws cloudformation execute-change-set --change-set-name arn:aws:cloudformation:us-east-1:123456789012:changeSet/SampleChangeSet/1a2345b6-0000-00a0-a123-00abc0abc000

CloudFormationのベストプラクティス

  • スタックは共通リソースの所有者でグルーピング

    • リソース利用の制限は行わないようにする
  • クロススタック参照で共有リソースをエクスポート
    • ネットワークリソース系
  • アクセス制限はIAMで
  • スタックの制限は200なので、注意
  • テンプレートの再利用
  • 他のスタックにネストされたテンプレートの参照
  • テンプレートに認証情報を埋め込まない
    • 埋め込んでもNoEchoプロパティを使う
  • cfn-init ヘルパースクリプトと AWS::CloudFormation::Init を使ってEC2にソフトウェアをインストールする
  • ヘルパースクリプトは常に最新のものを使う
  • テンプレートを使用する前に検証する
    • aws cloudformation validate-template
  • CloudFormationで全てのリソースを管理する
  • スタックを更新する前に変更セットを作成する
  • CloudTrailとロギング
  • コードの確認とリビジョン管理
  • Elastic Compute Cloudのパッケージは最新化する

CloudFormationのテンプレート

JSONかYAMLのどちらかでテンプレートを作成します。

テンプレートのセクション

  • Format Version(任意)

    • テンプレートバージョンを指定する
  • Desription(任意)
    • 説明文
  • Metadata(任意)
    • テンプレートに関する追加情報を提供するオブジェクト
  • Parameters(任意)
    • テンプレートに渡すことができる値を指定する

      • データ型、デフォルト値、最大最小値など型が設定可能
    • テンプレートのResources、Outputsセクションのパラメーターを参照できる
  • Mappings(任意)
    • 条件パラメーター値の指定に使用できる、キーと関連する値のマッピング(検索テーブルに類似したもの)
  • Conditions(任意)
    • 特定のリソースが作成されるかどうかや、スタックの作成または更新中に特定のリソースプロパティが値を割り当てられるかどうかを制御する条件を定義する
  • Transform(任意)
    • サーバレスアプリケーションの場合、使用するAWS SAMのバージョンを指定する
  • Resources(必須)
    • スタックで作成するリソースとそのプロパティを指定する

      • EC2やELBやRDSなど起動するサービスを指定し、リソース毎に決められたプロパティを指定する
  • Outputs(任意)
    • スタックのプロパティを確認すると返される値について説明する
  • Function
    • パラメータの参照やMapの参照などの際はFunctionを使う(Parameterの取得に利用するRef関数もFunctionの一つ)

      • Ref: パラメータの参照
      • Fn::Base64 : 文字列をBase64でエンコードする
      • Fn::FindInMap : Mapから値を取り出す
      • Fn::GetAtt :リソースに付随する値を取得する
      • Fn::GetAZs : 指定したリージョンのAZを取得する
  • 疑似パラメータ参照
    • 予め定義されたパラメータ群をRef関数で参照できる

      • AWS::Region,AWS::StackId,AWS::StackNameなど

スタックの出力値のエクスポート

  • スタック間で情報を共有するにはスタックの出力値をエクスポートする

    • 同じAWSアカウントとリージョンの他のスタックは、エクスポートされた値をインポートできる
    • WebサーバのサブネットやSecurityGroup IDをエクスポートする単一ネットワーキングスタックがあったとして、Webサーバのあるスタックは、それらのネットワーキングしリソースをインポートできる

テンプレート作成ツール

  • CloudFormer

    • 構築済みの環境からテンプレートを作成するWebツール
  • CloudFormation Designer
    • テンプレート内のリソースを可視化
  • Hava
  • Cloudcraft

CloudFormationの運用

  • 個別テンプレート、1スタック
  • 個別テンプレート、個別スタック
  • スタック間の連携

CloudFormationのリソースについて

リソース属性

CreationPolicy

  • CloudFormationが指定数の成功シグナルを受信するかまたはタイムアウト期間が超過するまでは、ステータスが作成完了にならないようにする

    • AutoScalingCreationPolicy
    • ResourceSignal

DeletionPolicy

  • スタックが削除された際にリソースを保持または (場合によっては) バックアップできる

    • Retain

      • スタック削除したくない場合は指定する

DependsOn

  • 特定のリソースが他のリソースに続けて作成されるように指定できる

UpdatePolicy属性

  • CloudFormationがAWS::AutoScaling::AutoScalingGroup リソースに対する更新を処理する方法を指定できる

    • AutoScalingReplacingUpdate

      • AutoScaling グループの置き換え更新を処理する方法を指定する時に使う
    • AutoScalingRollingUpdate
      • ローリング更新を使う場合指定する
    • AutoScalingScheduledAction
      • スケジュールされたアクションが関連付けられているAutoScalingグループを含むスタックを更新する時に使う

CloudFormationヘルパースクリプト

スタック作成の時にEC2インスタンスでソフトウェアをインストールしたりサービスを開始したりするために使用できるPythonヘルパースクリプトのセットがある

  • cfn-init

    • リソースメタデータの取得と解釈、パッケージのインストール、ファイルの作成およびサービスの開始で使用される
  • cfn-signal
    • スタック内の他のリソースと準備できたアプリケーションを同期できる
  • cfn-getmetadata
    • リソースに定義されたすべてのメタデータ、またはリソースメタデータの特定のキーやサブツリーへのパスを簡単に取得できる
  • cfn-hup
    • メタデータへの更新を確認し、変更が検出された時にカスタムフックを実行するデーモン

参考情報

サービス概要
よくある質問
ドキュメント
SlideShare

更新日時

  • 2017/04/30 初回投稿

続きを読む

【AWS】CloudWatch

はじめに

タダです。
AWS認定試験勉強のためにCloudWatchのドキュメントを読んだ自分用メモになります。
あくまで個人用のメモになるので、その点はご了承ください。
※違う内容書いているなどありましたらご指摘いただけると幸いです。
※随時アップデートがあれば更新していきます。

サービス概要

  • AWSリソースと、AWSで実行されているアプリをリアルタイムでモニタリングするマネージドサービス
  • ClouWatchアラームはユーザーが定義したルールに基づいて通知を送信したり、モニタリングしているリソースに自動的に変更を加えられる
    • EC2のCPU使用率およびディスク読み書きをモニタリングし、増加する負荷を処理する追加のインスタンスを追加のインスタンスを開始できるかをはんだんする
  • EC2のモニタリングタイプ
    • 基本モニタリング:無料、データは5分間隔
    • 詳細モニタリング:追加料金が必要、データは1分間隔
  • アラームのステータス
    • OK : 定義された閾値を下回ってる状態
    • アラーム : 定義された閾値を上回っている状態
    • 不足 : データが不足のため、状態を判定できない
      • データポイント自体が存在しない状態
      • OKとアラームはデータポイントが存在する状態で評価
        • したがって必ずしも不足状態が障害を表すステータスじゃない

専門用語

  • 名前空間

    • CloudWatchメトリクスのコンテナ。 AWS/ Service という命名規則。

      • ex: AWS/EC2、AWS/RDS、AWS/ELB
  • メトリクス
    • CloudWatchに発行された時系列のデータポイントのセット

      • データの粒度は最小が1分〜3分や5分間間隔のものまである
    • メトリクスはリージョンごとに管理される
    • 最大15ヶ月まで保管される
      • 期間が 60 秒 (1 分) のデータポイントは、15 日間使用できる
      • 期間が 300 秒 (5 分) のデータポイントは、63 日間使用できる
      • 期間が 3600 秒 (1 時間) のデータポイントは、455 日 (15 か月) 間使用できる
      • https://aws.amazon.com/jp/cloudwatch/faqs/
  • ディメンション
    • 監視の範囲

他のサービスとの連携

  • SNS

    • CloudWatchアラームを使う時に連携する
  • AutoScaling
    • ユーザー定義のポリシー、ヘルスステータスチェックおよびスケジュールに基づいてEC2を自動的に起動、終了できる
  • CloudTrail
    • CloudTrailログにCloudWatch API実行を記録できる
  • IAM
    • CloudWatchの操作権限を管理する

アクション機能

モニタリングの状態によってアクションを定義する

  • SNS通知
  • EC2アクション
    • EC2 Auto Recovery(対応するインスタンスはC3,4/M3/R3/T2で、EC2-Classicとハードウェア専有インスタンスは未対応)
    • EC2 再起動/停止/起動
  • AutoScaling

CloudWatch Events

AWSリソースの変更を示すシステムイベントのほぼリアルタイムのストリームを、EC2インスタンス、Lambda、SQSキュー、ECSタスク、ステップ関数ステートマシン、SNSトピック、Kinesisストリーム、組み込むターゲットに振り分けるのがCloudWatch Events

専門用語

  • イベント

    • AWSリソースにおける変化で、CloudWatch Eventsのトリガーとなる。
  • ターゲット
    • イベントを処理する
  • ルール
    • 一致した受信イベントを検出し、処理のためにターゲットに振り分ける

他のサービスとの連携

  • CloudTrail

    • CloudWatch Eventsのログをとれる
  • CloudFormation
    • CloudWatch Eventsのルールを作れる
  • Config
    • Config Rulesがトリガーされると、CloudWatch Eventsによってキャプチャできるイベントが生成される
  • Kinesis Stream
  • Lambda

イベントタイプ

  • EBSイベント

    • スナップショット通知
    • ボリューム通知
  • EC2イベント
    • インスタンスの状態変更通知
  • EC2 System Managerイベント
    • RunCommandの実行の通知
    • EC2 Automationステップステータス変更の通知
  • EC2メンテナンスウィンドウイベント
  • ECSイベント
    • ECSコンテナインスタンスの状態変更
    • ECSタスクの状態変更
  • EMRイベント
    • クラスター状態の変更
    • EMRステップ状態の変更
  • AutoScalingイベント
    • EC2インスタンス起動のライフサイクルアクション
    • EC2インスタンスの起動/失敗
  • APIコールイベント
  • CodeDeployイベント
    • デプロイ状態変更通知
  • AWSマネジメントコンソールサインインイベント
  • AWS Healthイベント
  • KMSイベント
  • スケジュールイベント
  • TrustedAdvisorイベント

CloudWatch Logs

  • CloudWatch Logsは、EC2、CloudTrailおよびその他のソースのログファイルの監視、保存、アクセスができる
  • 特徴として、EC2インスタンス/CloudTrailのログをリアルタイムでモニタリングできる他に、ログデータをアーカイブできる

専門用語

  • ログイベント

    • モニタリングされてるアプリまたはリソースによって記録されたアクティビティの記録
    • イベント発生時のタイムスタンプおよび生のイベントメッセージ(UTF-8)
  • ログストリーム
    • 同じリソースを共有する一連のログイベント(ログフォルダのサブフォルダのイメージ)
  • ロググループ
    • ログの出力箇所(ログフォルダのイメージ)
  • メトリクスフィルタ
    • ログイベントから特定の文字列のフィルタリングが可能
  • 保持設定
    • ログの保持期間を設定する

他のサービスとの連携

  • CloudTrail
  • Kinesis Streams
    • ITインフラのログデータ、アプリのログ、SNSメディア・マーケットデータフィード、ウェブのクリックストリームデータの取り込みと処理をリアルタイムでできる
    • CloudWatch Logsに収集されたログをリアルタイムにKinesis Streamに転送可能
  • Lambda
    • ログの出力先がCloudWatch Logs
  • S3
    • ログデータをS3にエクスポートする
    • ログデータはエクスポート出来るようになるまで最大12時間かかる場合ある

参考

更新日時

  • 2017/04/29 初回投稿

続きを読む

[AWS] Terraform で EFS 作って、EC2 起動時にマウントさせておく

Terraform を使うと、EFS を作成して EC2 にマウントさせておくなんてことが簡単にできます。
Autoscaling 環境で Web ドキュメントルートを共有したい時とかに便利なんで、みんな使えばいいと思うよ。
なお、この記事の想定読者は AWS はダッシュボードからポチポチしてインスタンス立てたりしてるけど、そろそろインフラをコードで管理したいな。Terraform とか便利そうだねー使ってみたいねーって人です。
てわけで、単純に EC2 立ち上げても面白くないので EFS をマウントさせてみました。

そもそも、Terraform ってなんだ?って人は、以下のページとか参考になると思います。
Terraform簡易チュートリアル on AWS

実際の設定

Terraform は、特定のディレクトリ下にある拡張子が .tf なファイルを全部読み込んでいい感じにリソースを起動してくれます。なので、機能別に .tf 作成していってみましょう。

メイン設定

まず、メインの設定を作成。
プロバイダーとか、設定ファイル内で使用する変数とか設定していってみましょうか。

main.tf
# 今回のプロジェクト名
variable "project" {}
variable "domain" {}

# AWS リソースを起動するリージョンとかの情報
variable "region" { default = "us-west-2" }
variable "azs" {
    default {
        "a" = "us-west-2a"
        "b" = "us-west-2b"
        "c" = "us-west-2c"
    }
}

# AMI ID (Amazon Linux)
variable "ami" { 
    default {
        "us-west-2" = "ami-8ca83fec"
    }
}

# EC2 接続用の SSH 鍵の公開鍵
variable "ssh_public_key" {}

provider "aws" {
    region = "${var.region}"
}

variable で設定した値は tf ファイル内で ${var.region} のようにして参照可能です。
また、terraform の各種コマンドを実行する際に以下のようにパラメータとして変数を渡して上書きすることもできます。

$ terraform plan \
  -var 'project=example' \
  -var 'domain=example.com'

同じディレクトリ内に terraform.tfvars というファイルがあれば、それを読み込んで値が上書きされたりします。この辺の詳細は以下を参照してください。
Input Variables – Terraform by HashiCorp

provider "aws" は、aws を使いますよって宣言です。
以下のように アクセスキーを書いておくこともできますが、それやるとうっかり github とかに公開した時とかに切ない目にあうのでやめたほうが吉でしょう。

provider "aws" {
    access_key = "__ACCESS_KEY__"
    secret_key = "__SECRET_KEY__"
    region = "us-west-2"
}

環境変数 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY を読み込んでいい感じでやってくれるので、僕は direnv 使って作業ディレクトリ内で環境変数を変更することで対応してます。
(もちろん、この場合でも .gitignore.envrc を含めておいて間違って公開しないようにしないと切ない目にあうので注意)

VPC の作成

こんな感じの .tf ファイルで VPC と subnet が作成できます。

vpc.tf
## VPC
resource "aws_vpc" "app" {
    cidr_block           = "172.31.0.0/16"
    enable_dns_hostnames = true
    enable_dns_support   = true
    instance_tenancy     = "default"

    tags {
        "Name" = "${var.project}"
    }
}

## Subnet
resource "aws_subnet" "a" {
    vpc_id                  = "${aws_vpc.app.id}"
    cidr_block              = "172.31.0.0/20"
    availability_zone       = "${lookup(var.azs,"a")}"
    map_public_ip_on_launch = true

    tags {
        "Name" = "${var.project}-subnet-a"
    }
}

resource "aws_subnet" "b" {
    vpc_id                  = "${aws_vpc.app.id}"
    cidr_block              = "172.31.16.0/20"
    availability_zone       = "${lookup(var.azs,"b")}"
    map_public_ip_on_launch = true

    tags {
        "Name" = "${var.project}-subnet-b"
    }
}

resource "aws_subnet" "c" {
    vpc_id                  = "${aws_vpc.app.id}"
    cidr_block              = "172.31.32.0/20"
    availability_zone       = "${lookup(var.azs,"c")}"
    map_public_ip_on_launch = true

    tags {
        "Name" = "${var.project}-subnet-c"
    }
}

resource "aws_subnet" の中に ${aws_vpc.app.id} ってのが出てきましたね。
Terraform の中では、管理下にあるリソースの情報を他のリソースの設定でも参照することが可能です。
各リソースで使用できる値が異なってくるので、その辺は公式ドキュメント読みましょう。
例えば aws_vpc で使用できる値は aws_vpc を参照すればわかります。

また、${lookup(var.azs,"a")} ってのも出てきましたね。
これは Terraform の組み込み関数です、lookup は配列の中からキーをもとに値を探す関数です。
詳しくは Built-in Functions を読んでください。

ついでに Internet Gateway と Route Table も設定しておきましょう。

route-table.tf
## Internet Gateway
resource "aws_internet_gateway" "igw" {
    vpc_id = "${aws_vpc.app.id}"
}

## Route Table
resource "aws_route_table" "rtb" {
    vpc_id     = "${aws_vpc.app.id}"
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.igw.id}"
    }
}

resource "aws_route_table_association" "route_a" {
    subnet_id = "${aws_subnet.a.id}"
    route_table_id = "${aws_route_table.rtb.id}"
}

resource "aws_route_table_association" "route_b" {
    subnet_id = "${aws_subnet.b.id}"
    route_table_id = "${aws_route_table.rtb.id}"
}

resource "aws_route_table_association" "route_c" {
    subnet_id = "${aws_subnet.c.id}"
    route_table_id = "${aws_route_table.rtb.id}"
}

IAM ロールの作成

次に EC2 に割り当てるための IAM ロールを作ってみましょう。
ポリシーは、AWS が用意している AmazonEC2RoleforDataPipelineRole と、EC2 から CloudwatchLogs にログを送信するためのカスタムポリシーを作ってアタッチしてみます。

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

## AmazonEC2RoleforDataPipelineRole
resource "aws_iam_role_policy_attachment" "data-pipeline" {
    role       = "${aws_iam_role.instance_role.name}"
    policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforDataPipelineRole"
}

## PutCloudwatchLogs
resource "aws_iam_policy" "put-cloudwatch-logs" {
    name        = "AmazonEC2PutCloudwatchLogs"
    description = ""
    policy      = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "put-cloudwatch-logs" {
    role       = "${aws_iam_role.instance_role.name}"
    policy_arn = "${aws_iam_policy.put-cloudwatch-logs.arn}"
}

aws_iam_roleassume_role_policy のところと、aws_iam_policypolicy のところでヒアドキュメントが出てきましたね。
こんな風に複数行にわたるインラインポリシーはヒアドキュメントで記述することが可能です。
また、以下のように別ファイルにしておいて読み込ませることも可能です。
管理しやすい方でやってください。

iam-role.tf
resource "aws_iam_role" "instance_role" {
    name               = "instance_role"
    path               = "/"
    assume_role_policy = "${file("data/instance_role_assume_policy.json")}"
}
data/instance_role_assume_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

セキュリティグループの作成

EC2 から EFS へのアクセスは 2049 番ポートを介して行われるので、EFS が所属するセキュリティグループに穴を開けないといけません。
EC2 は 80, 443, 22 を解放してみます。

security-group.tf
## For EC2
resource "aws_security_group" "ec2" {
    name        = "${var.project}-EC2"
    description = "for ${var.project} EC2"
    vpc_id      = "${aws_vpc.app.id}"

    ingress = [
        {
            from_port       = 80
            to_port         = 80
            protocol        = "tcp"
            cidr_blocks     = ["0.0.0.0/0"]
        },
        {
            from_port       = 443
            to_port         = 443
            protocol        = "tcp"
            cidr_blocks     = ["0.0.0.0/0"]
        },
        {
            from_port       = 22
            to_port         = 22
            protocol        = "tcp"
            cidr_blocks     = ["0.0.0.0/0"]
        }
    ]

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

## For EFS
resource "aws_security_group" "efs" {
    name        = "${var.project}-EFS"
    description = "for ${var.project} EFS"
    vpc_id      = "${aws_vpc.app.id}"

    ingress {
        from_port       = 2049
        to_port         = 2049
        protocol        = "tcp"
        security_groups = ["${aws_security_group.ec2.id}"]
    }

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

EFS の作成

こんな感じで EFS が作成できます。
各サブネットごとにマウントターゲットを作成して、そいつをセキュリティグループに所属させる形ですね。

efs.tf
resource "aws_efs_file_system" "app" {
  tags {
        "Name" = "${var.domain}"
  }
}

resource "aws_efs_mount_target" "app-a" {
  file_system_id  = "${aws_efs_file_system.app.id}"
  subnet_id       = "${aws_subnet.a.id}"
  security_groups = ["${aws_security_group.efs.id}"]
}

resource "aws_efs_mount_target" "app-b" {
  file_system_id = "${aws_efs_file_system.app.id}"
  subnet_id      = "${aws_subnet.b.id}"
  security_groups = ["${aws_security_group.efs.id}"]
}

resource "aws_efs_mount_target" "app-c" {
  file_system_id = "${aws_efs_file_system.app.id}"
  subnet_id      = "${aws_subnet.c.id}"
  security_groups = ["${aws_security_group.efs.id}"]
}

EC2 の作成

さて、いよいよ EC2 です。
ここでは、user-data を使って、初回ローンチ時に EFS をマウントさせてしまいます。
さらにマウントした EFS 内に html/ ってディレクトリを作成して、そいつを /var/www/html にシンボリックリンクしてみましょうか。
と言っても、こんな感じで大丈夫です。

ec2.tf
## IAM Instance Profile
resource "aws_iam_instance_profile" "instance_role" {
    name = "instance_role"
    role = "${aws_iam_role.instance_role.name}"
}

## SSH Key
resource "aws_key_pair" "deployer" {
  key_name   = "${var.project}"
  public_key = "${var.ssh_public_key}"
}

## EC2
resource "aws_instance" "app" {
    ami                         = "${lookup(var.ami,var.region)}"
    availability_zone           = "${aws_subnet.a.availability_zone}"
    ebs_optimized               = false
    instance_type               = "t2.micro"
    monitoring                  = true
    key_name                    = "${aws_key_pair.deployer.key_name}"
    subnet_id                   = "${aws_subnet.a.id}"
    vpc_security_group_ids      = ["${aws_security_group.ec2.id}"]
    associate_public_ip_address = true
    source_dest_check           = true
    iam_instance_profile        = "${aws_iam_instance_profile.instance_role.id}"
    disable_api_termination     = false

    user_data                   = <<USERDATA
#!/bin/bash
az="${aws_subnet.a.availability_zone}"
efs_region="${var.region}"
efs_id="${aws_efs_file_system.app.id}"
efs_mount_target="${aws_efs_mount_target.app-a.dns_name}:/"
efs_mount_point="/mnt/efs/$${efs_id}/$${az}"
web_doc_root="/var/www/html"

# EFS Mount
/usr/bin/yum -y install nfs-utils || /usr/bin/yum -y update nfs-utils
if [ ! -d $${efs_mount_point} ]; then
  mkdir -p $${efs_mount_point}
fi
cp -pi /etc/fstab /etc/fstab.$(date "+%Y%m%d")
echo "$${efs_mount_target}    $${efs_mount_point}   nfs4    defaults" | tee -a /etc/fstab
mount $${efs_mount_point}

# create Web document root
if [ -d $${web_doc_root} ]; then
  rm -rf $${web_doc_root}
fi
if [ ! -d $${efs_mount_point}/html ]; then
  mkdir $${efs_mount_point}/html
  chown ec2-user:ec2-user $${efs_mount_point}/html
fi
ln -s $${efs_mount_point}/html $${web_doc_root}
chown -h ec2-user:ec2-user $${web_doc_root}
USERDATA

    root_block_device {
        volume_type           = "gp2"
        volume_size           = 10
        delete_on_termination = true
    }

    tags {
        "Name"          = "${var.domain}"
    }
}

user_data は長めのシェルスクリプトなので、可読性が悪いから ${file("data/user_data.sh")} とかってやって別ファイルで管理したいですよね。
でも待ってください、ヒアドキュメントでやってるのは理由があるのです。

ヒアドキュメントで書くと、user_data 用のシェルスクリプトの中で Terraform の変数が使えます。
マウントするには EFS の ID とか、マウントターゲットの dns_name とか必要になってきますが、それを作成前に知らなくてもこのように書いておけるのです。便利ですね。
その代わり、user_data 用のシェルスクリプト内でローカルな環境変数を使いたい場合は $${efs_mount_point} のように書いてあげてくださいね。

ざっと、こんな感じです。
慣れちゃえば、tf ファイルを使い回しできるので便利ですよ。
また、すでに作成済みの AWS リソースを Terraform 管理下に置きたい場合は

$ terraform import aws_instance.app ${instance_id}

のようにして管理下に置くことができます。
管理されているリソースは terraform.tfstate というファイルに書き込まれます。
さらに別プロダクトになるのですが Terraforming と言うツールを使用すると、既存の AWS リソースから Terraform 用の tf ファイルを作成したり、terraform.tfstate を作成したりもできるので便利です。
Terraforming については、Terraforming で既存のインフラを Terraform 管理下におく を参考にしてください。

実際にリソース作成してみる

tf ファイル書いたら

$ terraform plan

で、設定ファイルに誤りがないか?既存のリソースへの影響はどの程度あるのかが確認できます。
実際に反映させたい時は

$ terraform apply

で、おっけ。

では、良い Terraform を!

続きを読む

全力AWSでMastodonサーバー立てました chitose.moe

https://chitose.moe/

スポンサーが1社付いてるのですぐに消えたりはしないはず。
メール送信数制限の都合で1日のユーザー登録数には上限がある。
メールが届かない時は暫く待つか24時間後に再送信。
とはいえ5万なので上限になることはないか。

環境

  • AWS
  • EC2 Container Service
  • ecs-cli
  • RDS(PostgreSQL)
  • ElastiCache(Redis)
  • S3
  • CloudFront
  • ALB
  • SES

やれるだけやって分散した。
いくらでもサーバー増やせるけどたぶんそこまで必要にはならない。

最後SESのsandbox解除待ちで時間かかった…。

途中段階の役に立たないメモ

ローカルで動かすだけならVagrantのほうが早い。
最初から管理者アカウントも作られる。

docker-composeはproduction用。

git clone https://github.com/tootsuite/mastodon
cd mastodon
sudo curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-darwin-amd64-latest
sudo chmod +x /usr/local/bin/ecs-cli
ecs-cli help
ecs-cli configure -p default --cluster mastodon
ecs-cli up ...
aws ecr get-login
docker login ...
aws ecr create-repository --repository-name mastodon
docker build -t mastodon .
docker tag mastodon:latest ...
docker push ...

docker-compose.ymlを編集
– buildの代わりにimage: mastodon
– DB永続化するためコメントを消す

ecs-cli compose service up

ここで上手く動かなくなった。
Elastic Beanstalkでやろうとしたけどこっちもだめ。
いろいろやってるうちにオリジナルのdocker-compose.ymlが事前に用意したDocker image使うようになってた。
ECS使う方法に戻る…。

assetsを削除して再生成してS3にアップ。
precompileを繰り返すとファイルが増える…?

rm -rf ./public/assets
docker-compose run --rm web rails assets:precompile

aws s3 cp ./public/assets s3://{S3バケット}/assets --recursive --acl public-read --cache-control "max-age=604800"

S3に置くと/public/assets以下がないのでprecompile済みファイルが使われない。
sprockets-manifestが必要。
S3に置くのはやめた。
image: gargron/mastodonは使わず自分でビルドする方法に戻る…。

.dockerignoreを変更してimageにassetsも含まれるように。

#public/assets

docker-compose.ymlをコピーしてaws.ymlを作りこっちを書き換えて行く。
docker-compose.ymlはローカル用に元のまま。
あ、当然gitのブランチは分けてる。

AWSのECSでは

  • buildが使えない
  • volumesで相対パスが使えない

という仕様なのでそれに合わせる。
volumesはよくわからないのでほぼ使わないように…。

ここからも色々苦労したけど細かすぎてもう忘れたのでメモ程度。
AWSのサービスで使えるものは使う。
db,redisはもちろん分離。
S3も当たり前に使う。
nginxは不要だった。nginx使おうとして無駄に混乱…。
ポートマッピングはELB。
httpsへのリダイレクトはCF。

LOCAL_HTTPS=falseでも問題なくhttpsで動くけどメール内のURLだけhttpになる。
リダイレクトされるので妥協。

ecs-cli compose serviceで--load-balancer-name付きで起動だけどうしてもできなかったので諦めた。

/api/v1/streamingはサブドメインにした。
STREAMING_API_BASE_URLで指定すればそれが使われる。
LOCAL_HTTPS=falseだとwebsocketでエラー出てたので無理矢理な対応。

S3_BUCKETS3_HOSTNAMEはS3のドメインそのまま使う用で
自分のサブドメイン使う場合はS3_CLOUDFRONT_HOSTも設定する。
CF使ってるかに関係なく静的ホスティングなら。

nginx使わなくても動いてるけど何か問題あれば後で修正していく。

docker-compose.ymlあるから簡単に動かせるかと思ったけどそんなことはなかった。
EC2上でdocker-compose upすれば簡単だけどそれじゃAWSの意味がない。

ALBの使用

その後調べて分かったので追記。
--load-balancer-nameはClassic Load Balancer用なので違う。
ALBは--target-group-arnを使う。
ただしサービスごとに一つしか設定できないのでサービスを複数作るしかない?

ecs-cli compose -f aws.yml --project-name mastodon-web service create --target-group-arn {web用のターゲットグループ} --container-name web --container-port 3000 --role ecsServiceRole

ecs-cli compose -f aws.yml --project-name mastodon-api service create --target-group-arn {streaming用のターゲットグループ} --container-name streaming --container-port 4000 --role ecsServiceRole

--project-nameの指定も必要になった。

ecs-cli compose -f aws.yml --project-name mastodon-web service up

AutoScalingでEC2インスタンス数を増減。
一つのEC2内で複数のタスクが動くし、タスクもAutoScalingできるようになった。

サービスを分けるならaws.ymlも分割したほうがメモリの無駄もない。

最終版

ごちゃごちゃしたのでまとめ。

aws-web.yml

mem_limitは分からないので仮。
portsの0が動的ポートのために必要。
もしくは0:部分なしでいいかも。こっちは未検証。

version: '2'
services:

  web:
    restart: always
    image: {自分のimage}
    env_file: .env.production
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    mem_limit: 536870912
    ports:
      - "0:3000"

aws-api.yml

version: '2'
services:
  streaming:
    restart: always
    image: {自分のimage}
    env_file: .env.production
    command: npm run start
    mem_limit: 268435456
    ports:
      - "0:4000"

  sidekiq:
    restart: always
    image: {自分のimage}
    env_file: .env.production
    command: bundle exec sidekiq -q default -q mailers -q pull -q push
    mem_limit: 268435456

ALB

ターゲットグループ

  • port3000のweb用
  • port4000のstreaming用

を作る。

リスナーHTTPS 443
ヘルスチェックのポートをトラフィックポートにする。

  • HostがAPI用のサブドメインならstreaming
  • Pathが/ならweb

というルールを設定する。

CloudFrontはこのALBをOriginにする。
/assets以下はキャッシュ強く。他は短め。

Route53はCloudFrontを指定。

今後のためのアップデート手順

masterブランチを最新にしてからマージ。

DB_HOSTはRDSを指定してるのでdb:migrateはローカルから直接行う。
これはどうなんだろうとは思うけど他の方法が分からなかった。

docker-compose build
docker-compose run --rm web rails db:migrate
docker-compose run --rm web rails assets:precompile

docker imageの更新はECRの手順通りに。

aws ecr get-login --region ap-northeast-1 | bash

docker build ...
docker tag ...
docker push ...

ecs-cli使ったほうが少し短いかも。

docker build ...
docker tag ...

ecs-cli push ...

後はup。upはymlが変更されてないと以前のタスクのままなのでimageだけ更新した場合は更新されない。
CIで動かしてdocker tagを設定してymlの書き換えまで自動化かな。

ecs-cli compose -f aws-web.yml --project-name mastodon-web service up
ecs-cli compose -f aws-api.yml --project-name mastodon-api service up

動きさえすれば運用段階では楽になる。

続きを読む

jqでちょっぴり複雑な検索をする

 jqコマンドで配列の要素かつ複雑な構造をもつ要素(ここではaws ec2 describe-instancesの結果)に対して検索を行ってみました。

はじめに

 株式会社アイリッジのCommon Lisp大好きサーバサイドエンジニア、tanaka.lispです。

 AWS EC2を利用していると、特定のインスタンスの情報(インスタンスIDやインスタンスタイプ、などなど…)を知りたくなることがあります。ただ、そのためだけに管理コンソールをぽちぽちして取得していると、なんだか負けた気がするし(これ大事)、シェルスクリプトなどで結果を利用するときに不便です。

 awscliaws ec2 describe-instancesで取得した結果をjqに食わせ、インスタンスにつけている名前からイイかんじに検索・整形したいところですが、awscliはけっこう複雑な返り値を返してきますよね…:

{
  "Reservations": [
    "Instances": [
      {
        "InstanceId": "i-commonlisp",
        "Tags": [
          {
            "Key": "Name",
            "Value": "app.commonlisp"
          },
          {
            "Key": "aws.cloudformation:stack-name",
            "Value": "stack.commonlisp"
          }
        ],
        ...
      },
      ...
    ],
    ...
  ]
}

 そこで、名前(TagsKeyNameの値)によるインスタンスの検索方法を試行錯誤しました。

よういするもの

  • jq: jq-1.5-1-a5b5cbe
  • awscli: aws-cli/1.11.58 Python/3.6.0 Linux/4.8.0-41-generic botocore/1.5.21

jqでの検索

 jqでは入力のJSONに対して、以下のようなことができます:

  • 入力JSONの整形 ({"id": .InstanceId, "tags": .Tags})
  • 入力JSONからのデータ抽出 (select)

 今回はこれらを組み合わせて、aws ec2 describe-instancesの結果から探しているインスタンスのインスタンスID(InstanceId)を取得してみます。

入力の整形

 たとえばこんなJSONオブジェクトがあって、

[
  {
    "name": "Lisp",
    "developer": "John McCarthy"
  },
  {
    "name": "Common Lisp",
    "developer": "ANSI X3J13 committee",
    "spec": "ANSI INCITS 226-1994 (R2004)"
  },
  {
    "name": "Arc",
    "developer": "Paul Graham",
    "books": [
      "On Lisp",
      "Hackers and Painters"
    ]
  },
  {
    "name": "Clojure",
    "developer": "Rich Hicky"
  }
]

言語名(lang)と開発者名(developer)だけがほしいとき、入力をprintf的に整形できます。

$ echo '...上のJSON...' \
   | jq '.[] | {"lang": .name, "developer": .developer}'
{
  "lang": "Lisp",
  "developer": "John McCarthy"
}
{
  "lang": "Common Lisp",
  "developer": "ANSI X3J13 committee"
}
{
  "lang": "Arc",
  "developer": "Paul Graham"
}
{
  "lang": "Clojure",
  "developer": "Rich Hicky"
}

要素の抽出のようにも思えますが、別にもっと抽出っぽいオペレータがあるので、こちらは整形と呼びました。

入力からのデータ抽出

 条件を満たすもののみをpass throughするというオペレータもあって、それがselectです。こちらを抽出とここでは呼んでいます。

 manにあることがすべてなのですが、

   select(boolean_expression)
       The function select(foo) produces its input unchanged if foo returns true for that input, and produces no output otherwise.

       It´s useful for filtering lists: [1,2,3] | map(select(. >= 2)) will give you [2,3].

正規表現を組み合わせたりできるので強力です;

$ echo '...上のJSON...' \
  | jq '.[] | .name |select( .| test("Lisp"))'
"Lisp"
"Common Lisp"

実際に抽出

Exactlyな名前でインスタンスの検索

 では実際に検索クエリをつくってみます。

 まずはインスタンスリストを得て、

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]'
{
  "ImageId": "ami-commonlisp",
  "State": {
    "Code": 16,
    "Name": "running"
  }
}
{
...  # ずらずら出るので省略

そこにTagsKeyNameValueがExactlyapp.commonlispの要素だけ真になる条件を加えます(ルシのファルシがパージでコクーン感ある)。

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
{
  "InstanceId": "i-commonlisp",
  ...  # ずらずら出るので省略
}

 ついでに、不要な要素は出ないようにしましょう。

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}

名前うろおぼえインスタンスの検索

 なんかappって名前をつけたような気がするけどなー、まったく思い出せない。俺たちは雰囲気でサーバを立てている。そんなアナタに捧ぐ。主にぼく自身に捧ぐ :angel:

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select( .Tags[].Key == "Name" and (.Tags[].Value | test("^app")))
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}
{
  "instance-id": "i-clojure",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.clojure"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "clojure-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}

おわりに

 jqでの検索について、以下のことを述べました:

  • 要素を検索する基本的な方法
  • 対象要素のさらに中の辞書も検索条件にする方法

 jqってよくできたDSLですね。jqはチューリング完全でもあるらしく、まさしくJSON時代のawk感があります。


 ところでjqで検索するだけなのになぜ記事が長くなるのか :sob:

続きを読む