AWS メンテナンス対象を抽出するスクリプト

  • redmine 貼り付け用
  • region は適宜
#! /bin/bash
echo "|_. Name|_. ID|_. Event|_. Start|_. End|";aws ec2 describe-instance-status --region us-east-1 --filters "Name=event.code,Values=*" --query 'sort_by(InstanceStatuses[].[InstanceId,Events[0].Code,Events[0].NotBefore,Events[0].NotAfter],&[2])' --output text|xargs -I{} bash -c 'line="{}";id=$(echo $line|cut -d" " -f1);name=$(aws ec2 describe-instances --instance-ids $id --query "Reservations[].Instances[].[Tags[?Key==`Name`].Value|[0]]" --output text);echo -e "|$name|${line//     /|}|"'

続きを読む

awsでruby on railsとapacheとの連携構築方法

すべての手順

必要なミドルウェアのインストール

$ sudo yum update
$ sudo yum install curl-devel ncurses-devel gdbm-devel readline-devel sqlite-devel ruby-devel
$ sudo yum install gcc gcc-c++ openssl-devel zlib-devel make patch git gettext perl rpm-build libxml2

rbenvのインストール

$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:${PATH}"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ env | grep RBENV
RBENV_SHELL=bash
$ rbenv --version
rbenv 1.1.0-2-g4f8925a

ruby2.4.0 のインストール

$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv install -l
.....
2.3.0-dev
  2.3.0-preview1
  2.3.0-preview2
  2.3.0
  2.3.1
  2.3.2
  2.3.3
  2.4.0-dev
  2.4.0-preview1
  2.4.0-preview2
  2.4.0-preview3
  2.4.0-rc1
  2.4.0
  2.5.0-dev
  jruby-1.5.6
  jruby-1.6.3
  jruby-1.6.4
  jruby-1.6.5
  jruby-1.6.5.1
...
$ rbenv install -v 2.4.0
$ rbenv rehash
$ rbenv global 2.4.0
$ ruby -v
 ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

Railsのインストール

$ gem update --system
$ gem install bundler --no-rdoc --no-ri
$ gem install --no-ri --no-rdoc rails
$ rbenv rehash    
$ rails -v
Rails 5.0.2
$ gem install bundler

sqlite3のインストール

$ wget https://www.sqlite.org/2017/sqlite-autoconf-3170000.tar.gz
$ tar xvzf sqlite-autoconf-3170000.tar.gz
$ cd ./sqlite-autoconf-3170000
$ ./configure
$ make
$ make install
$ source ~/.bash_profile
$ rm -rf sqlite-autoconf-317000*
$ sqlite3 --version
; 3.17.0 2017-02-13.....

Hello World アプリ作成

$ cd ~
$ rails new hello_world
$ cd hello_world
$ vim Gemfile
$ bundle install
- # gem 'therubyracer', platforms: :ruby
+ gem 'therubyracer', platforms: :ruby
$ bundle exec rails s -e production
=> Booting Puma
=> Rails 5.0.2 application starting in production on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.8.2 (ruby 2.4.0-p0), codename: Sassy Salamander
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

apache2.4のインストール

$ sudo yum install httpd24 httpd24-devel

Passengerのインストール

$ gem install passenger
$ rbenv rehash
$ sudo dd if=/dev/zero of=/swap bs=1M count=1024
$ sudo mkswap /swap
$ sudo swapon /swap
$ passenger-install-apache2-module
.....
linking shared-object passenger_native_support.so

--------------------------------------------
Almost there!

Please edit your Apache configuration file, and add these lines:

   LoadModule passenger_module /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2/buildout/apache2/mod_passenger.so
   <IfModule mod_passenger.c>
     PassengerRoot /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2
     PassengerDefaultRuby /home/ec2-user/.rbenv/versions/2.4.0/bin/ruby
   </IfModule>

After you restart Apache, you are ready to deploy any number of web
applications on Apache, with a minimum amount of configuration!
....

Apacheの設定

$ sudo chown -R apache. ~/hello_world

$ sudo vim /etc/httpd/conf.d/passenger.conf
+   LoadModule passenger_module /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2/buildout/apache2/mod_passenger.so
+   <IfModule mod_passenger.c>
+     PassengerRoot /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2
+     PassengerDefaultRuby /home/ec2-user/.rbenv/versions/2.4.0/bin/ruby
+   </IfModule>
$ sudo vim /etc/httpd/conf.d/apps.conf
 <VirtualHost *:80>
    ServerAlias ip address
    ServerName ip address

    <Directory "/home/ec2-user/hello_world/public/">
        PassengerAppRoot /home/ec2-user/hello_world
        RailsEnv production        
        Options -MultiViews
        Options Indexes FollowSymLinks
        Require all granted
    </Directory>
 </VirtualHost>

Apacheの起動

$ sudo service httpd start
$ sudo service httpd status

続きを読む

CloudFormationを使って、CloudWatchLogsフィルタ作成しアラート送信

参考にしたテンプレート

Using an AWS CloudFormation Template to Create CloudWatch Alarms

カスタマイズしたテンプレート

個人的には以下のアラームで十分でした。

  • AWSマネジメントコンソールへのログイン失敗アラーム
  • IAM権限によるアクセス拒否アラーム
  • セキュリティグループ作成・変更・削除アラーム
  • EC2インスタンス作成・起動・削除・停止・再起動アラーム
CloudWatch_Alarms_for_CloudTrail_API_Activity_customize.json
{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "AWS CloudTrail API Activity Alarm Template for CloudWatch Logs",
  "Parameters" : {
      "LogGroupName" : {
          "Type" : "String",
          "Default" : "CloudTrail/DefaultLogGroup",
          "Description" : "Enter CloudWatch Logs log group name. Default is CloudTrail/DefaultLogGroup"
      },
      "Email" : {
          "Type" : "String",
          "Description" : "Email address to notify when an API activity has triggered an alarm"
      }
  },
  "Resources" : {
      "SecurityGroupChangesMetricFilter": {
          "Type": "AWS::Logs::MetricFilter",
          "Properties": {
              "LogGroupName": { "Ref" : "LogGroupName" },
              "FilterPattern": "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }",
              "MetricTransformations": [
                  {
                      "MetricNamespace": "CloudTrailMetrics",
                      "MetricName": "SecurityGroupEventCount",
                      "MetricValue": "1"
                  }
              ]
          }
      },
      "SecurityGroupChangesAlarm": {
          "Type": "AWS::CloudWatch::Alarm",
          "Properties": {
              "AlarmName" : "SecurityGroupChanges",
              "AlarmDescription" : "セキュリティグループ作成・変更・削除アラーム",
              "AlarmActions" : [{ "Ref" : "AlarmNotificationTopic" }],
              "MetricName" : "SecurityGroupEventCount",
              "Namespace" : "CloudTrailMetrics",
              "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
              "EvaluationPeriods" : "1",
              "Period" : "300",
              "Statistic" : "Sum",
              "Threshold" : "1"
          }
      },
      "EC2InstanceChangesMetricFilter": {
          "Type": "AWS::Logs::MetricFilter",
          "Properties": {
              "LogGroupName": { "Ref" : "LogGroupName" },
              "FilterPattern": "{ ($.eventName = RunInstances) || ($.eventName = RebootInstances) || ($.eventName = StartInstances) || ($.eventName = StopInstances) || ($.eventName = TerminateInstances) }",
              "MetricTransformations": [
                  {
                      "MetricNamespace": "CloudTrailMetrics",
                      "MetricName": "EC2InstanceEventCount",
                      "MetricValue": "1"
                  }
              ]
          }
      },
      "EC2InstanceChangesAlarm": {
          "Type": "AWS::CloudWatch::Alarm",
          "Properties": {
              "AlarmName" : "EC2InstanceChanges",
              "AlarmDescription" : "EC2インスタンス作成・起動・削除・停止・再起動アラーム",
              "AlarmActions" : [{ "Ref" : "AlarmNotificationTopic" }],
              "MetricName" : "EC2InstanceEventCount",
              "Namespace" : "CloudTrailMetrics",
              "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
              "EvaluationPeriods" : "1",
              "Period" : "60",
              "Statistic" : "Sum",
              "Threshold" : "1"
          }
      },
      "ConsoleSignInFailuresMetricFilter": {
          "Type": "AWS::Logs::MetricFilter",
          "Properties": {
              "LogGroupName": { "Ref" : "LogGroupName" },
              "FilterPattern": "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }",
              "MetricTransformations": [
                  {
                      "MetricNamespace": "CloudTrailMetrics",
                      "MetricName": "ConsoleSignInFailureCount",
                      "MetricValue": "1"
                  }
              ]
          }
      },
      "ConsoleSignInFailuresAlarm": {
          "Type": "AWS::CloudWatch::Alarm",
          "Properties": {
              "AlarmName" : "ConsoleSignInFailures",
              "AlarmDescription" : "AWSマネジメントコンソールへのログイン失敗アラーム",
              "AlarmActions" : [{ "Ref" : "AlarmNotificationTopic" }],
              "MetricName" : "ConsoleSignInFailureCount",
              "Namespace" : "CloudTrailMetrics",
              "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
              "EvaluationPeriods" : "1",
              "Period" : "300",
              "Statistic" : "Sum",
              "Threshold" : "3"
          }
      },

      "AuthorizationFailuresMetricFilter": {
          "Type": "AWS::Logs::MetricFilter",
          "Properties": {
              "LogGroupName": { "Ref" : "LogGroupName" },
              "FilterPattern": "{ ($.errorCode = \"*UnauthorizedOperation\") || ($.errorCode = \"AccessDenied*\") }",
              "MetricTransformations": [
                  {
                      "MetricNamespace": "CloudTrailMetrics",
                      "MetricName": "AuthorizationFailureCount",
                      "MetricValue": "1"
                  }
              ]
          }
      },
      "AuthorizationFailuresAlarm": {
          "Type": "AWS::CloudWatch::Alarm",
          "Properties": {
              "AlarmName" : "IAMAuthorizationFailures",
              "AlarmDescription" : "IAM権限によるアクセス拒否アラーム",
              "AlarmActions" : [{ "Ref" : "AlarmNotificationTopic" }],
              "MetricName" : "AuthorizationFailureCount",
              "Namespace" : "CloudTrailMetrics",
              "ComparisonOperator" : "GreaterThanOrEqualToThreshold",
              "EvaluationPeriods" : "1",
              "Period" : "300",
              "Statistic" : "Sum",
              "Threshold" : "1"

          }
      },
        "AlarmNotificationTopic": {
          "Type": "AWS::SNS::Topic",
          "Properties": {
              "TopicName" : "CloudTrail-Alarm-AlarmNotification",
              "DisplayName" : "CloudTrailAlarm",
              "Subscription": [
                  {
                      "Endpoint": { "Ref": "Email" },
                      "Protocol": "email"
                  }
              ]
          }
      }
  }
}

続きを読む

EC2 タグに repo名/ブランチ/スクリプトパスを書いておいて GitHub push に勝手に反応させる

GitHub への push イベントを SNS で受け取って、「<リポジトリ名>:<ブランチ名>:<実行スクリプト>」というタグが付いている EC2 インスタンスを探して指定スクリプトを実行する、という話です。

で、スクリプト内で “git pull” するもヨシ、追加でごにょごにょするもヨシ。

前置き

CircleCI とかそんなんでなく、GitHub に push したら pull してくれればそれでいいんですよ、というケースは地味に多いわけです。

これは GitHub > SNS > Lambda > Run Command でスクリプト実行すれば良いのですが、「じゃぁリポジトリ毎に Lambda ファンクション作るの?」って話で。

そこで EC2 タグに関連するリポジトリ名とブランチ名、実行スクリプトを書いておけば 1つの lambda が全て面倒みてくれるようにします。

リポジトリが増えた時、サーバが増えた時も EC2 インスタンスにタグ付けるだけなので簡単です。

仕組み

DropShadow ~ 3.png

GitHub > SNS > Lambda はいろいろな方がやられている通り。
あとは EC2 インスタンスに

  • key = GitHub-Webhook
  • val = <リポジトリ名>:<ブランチ名>:<実行スクリプト>

というタグをつけておいて、lambda で対象 EC2 インスタンスを探して Rum Command でスクリプトを実行する。
で、スクリプトの中で git pull とか好きにせい、という構成。

例えば repo_1 というリポジトリの master に push したら、「key=GitHub-Webhook、val=repo_1:master:/hoge/hoge.sh」というタグが付いている EC2 インスタンスの /hoge/hoge.sh を実行します。

作る

SSM エージェントをインストールする

Run Command を実行するには対象インスタンスに SSM エージェントを入れておく必要があります。

SSMエージェントのインストール

40秒で支度しましょう。

$ cd /tmp
$ curl https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm -o amazon-ssm-agent.rpm
$ sudo yum install -y amazon-ssm-agent.rpm

EC2ロール割り当て

で、EC2 インスタンスに AmazonEC2RoleforSSM ポリシーをアタッチしておく。

001.png

SNS topic、GitHub webhook 専用 IAM ユーザの作成、GitHub の設定

「github-webhook」という SNS topic を作ります。
CLI でシュッと。

$ aws sns create-topic --name github-webhook

GitHub webhook 用に「github-webhook」という名前の IAM ユーザを作って、以下のようなインラインポリシーを付ける。
つまり github-webhook topic にパブリッシュする事しかできないユーザ。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "sns:Publish"
            ],
            "Sid": "Stmt0000000000000",
            "Resource": [
                "[さっき作った SNS の ARN]"
            ],
            "Effect": "Allow"
        }
    ]
}

で、GitHub 側もぱぱっと。

DropShadow ~ qiita_002.png

Lambda 準備

特になんもしてない。
エラー処理もないのでやさしく使う。

# -*- coding: utf-8 -*-

import json
import boto3

# key="GitHub-Webhook", val=<repoName>:<branchName>:<command>
# というタグを持つインスタンスを列挙する。
def search_target_instance(repoName, branchName):
    # 実行中の EC2 インスタンスを取得
    ec2 = boto3.client('ec2')
    response = ec2.describe_instances(
        Filters=[
            {
                'Name': 'instance-state-name',
                'Values': ['running']
            }
        ]
    )

    targetInstances = []

    for resv in response['Reservations']:
        for instance in resv['Instances']:
            for tag in instance['Tags']:
                if tag['Key'] == 'GitHub-Webhook':
                    tagValue = tag['Value']

                    if tagValue.startswith(repoName + ":" + branchName + ":"):
                        target = instance['InstanceId'], tagValue.split(':')[-1]
                        targetInstances.append(target)

    return targetInstances

# Run Command で指定スクリプトを実行する。
def execute_command(instanceId, command):
    print instanceId + " : " + command
    ssm = boto3.client('ssm')

    ssmRes = ssm.send_command(
        InstanceIds = [instanceId],
        DocumentName = "AWS-RunShellScript",
        Parameters = {
            "commands": [ command ]
        })

    commandId = ssmRes['Command']['CommandId']
    return commandId

# Lambda エントリポイント
def lambda_handler(event, context):
    message = event['Records'][0]['Sns']['Message']
    gitHubJson = json.loads(message)

    # SNS からリポジトリ名とブランチ名を取得する。
    repoName = gitHubJson["repository"]["name"]
    branchName = gitHubJson["ref"].replace('refs/heads/', '')

    # デプロイ対象 EC2 インスタンスを取得
    targetInstances = search_target_instance(repoName, branchName)

    # EC2 タグで指定されているコマンドを実行する
    for instance in targetInstances:
        instanceId, command = instance
        print execute_command(instanceId, command)

    return {'status': 'ok'}

EC2 タグつけて push

ココまで来たら、タグ付けて GitHub に push すればよい。
Run Command の結果は AWS コンソールの EC2 / Run Command から確認できます。

002.png

※ スクリプトは root で実行されるので注意。

結果はどうやって知るのよ?

本当は list_command_invocations() で Run Command の結果をみて Slack に通知とかすべきですが長くなるので割愛。

とりあえずスクリプト内から curl で slack に post しても良いかもしれないですね。

結論

大切なこと:「ssh ログインなんか絶対にしないぞ!」という強い意思。

続きを読む

AWS Batchを使ってバッチ処理を実装してみた。

今回やりたかったこと

  • 社内で適用しているセキュリティのSaaSサービスのアップデートの自動化をしたい
  • 処理内容は、基本的にapiをコールしてこねこねしていくだけ

Lambdaでいいのでは?

  • SaaSが提供しているapiサーバーが、アクセス集中時だと5分経ってもレスポンスを返してくれない

そこでAWS BATCH

AWS BATCHとは

  • Job queueを受けた段階で、予め指定しておいたスペック(スペックが足りなかったら自動で最適なものを立ててくれるらしい?)のEC2を立ち上げてECRからコンテナイメージを持ってきてタスク実行してくれる
  • スケジュールを設定して実行してくれる!とかは無さそう。

アーキテクチャ

スクリーンショット 2017-03-19 22.05.34.png

Dockerfileはこんな感じ

FROM centos:latest
RUN curl -kl https://bootstrap.pypa.io/get-pip.py | python
RUN pip install awscli
RUN yum install -y git
ADD init.sh /opt/
CMD ["sh","/opt/init.sh","test"]

事前に処理実行に必要なコードだったりシェルスクリプトなんかは、ECRに含めておく。
シェルには、コードコミットから実行ソースをcloneするのに必要なssh key(事前にKMSとかで暗号化しておいたもの)、復号化処理、処理実行とかを書いておく。

あとは、Job queueを送ってあげれば、自動でEC2を立ち上げ、コンテナをマウントして最新のソースコードをcloneしてきて、処理を実行してくれます。
自動で立ち上がったEC2はタスク処理終了後(CMDのとこのやつ)、1時間単位の課金が切れるタイミング?で自動で削除されます。(立ち上がりっぱなしとかの心配なし!)

job queueに送信
aws --profile <profile> --region <region> batch submit-job --job-name <好きな名前> --job-queue <job queueの名前> --job-definition <定義したjob>

結果については、 Jobsの画面で確認することができます。
スクリーンショット 2017-03-19 22.17.59.png

まとめ

先日参加した「JAWS-UG KOBE」のコンテナまつりでHiganWorks sawanobolyさんがElasticDockerRunって呼んでるって言ってました。
DockerImageを基にいい感じに実行してくれるが、逆にBatch?なのかって印象でした。
もし、スケジュール実行で定期的にbatch処理をしたい!とかでれば、lambdaなんかを使ってqueueに送信してやるといいかなぁと思いました。

追記

シェル内にkmsで暗号化したssh keyを含めているのですが、出来れば外出ししたい。。
パラメーターストアとかに出せるかと思ったのですが、文字数制限の関係で無理でした。

続きを読む

AWSの各サービスを雑に紹介する

えー、投稿しておいて何ですが、本稿本当に雑ですので、ご利用にあたってはあくまで自己責任ということで、よろしくお願いします。

コンピューティング

  • Elastic Compute Cloud (EC2)
    仮想専用サーバ、従量課金制 ≫公式

  • EC2 Container Registry (ECR)
    DockerHubみたいなやつ ≫英語公式 / Google翻訳 ≫Developers.IO

  • EC2 Container Service (ECS)
    Dockerオーケストレーション(デプロイ、起動停止制御) ≫公式 ≫@IT

  • Lightsail
    仮想専用サーバ、定額制 ≫公式

  • AWS Batch
    ECS対応バッチジョブスケジューラ ≫公式 ≫公式 ≫Developers.IO

  • Elastic Beanstalk
    プログラム実行環境 (Java, PHP, .NET, Node.js, Python, Ruby)、EC2を使用 ≫公式 ≫YouTube

  • AWS Lambda
    プログラム実行環境 (Node.js, Java, C#, Python)、サーバレス ≫公式

  • Auto Scaling
    EC2対応オートスケール制御 ≫公式

  • Elastic Load Balancing
    負荷分散、BIG-IPとかその手のヤツのクラウド版 ≫公式 ≫@IT

ストレージ

  • Amazon Simple Storage Service (S3)
    オブジェクトストレージ。ファイルサーバとしても一応使える ≫公式

  • Amazon Elastic Block Store (EBS)
    ブロックデバイス ≫CodeZine

  • Elastic File System (EFS)
    ファイルサーバ ≫公式

  • Glacier
    バックアップストレージ ≫公式

  • Snowball
    HDDをFedExで送るオフラインデータ転送

  • Storage Gateway
    バックアップデバイスはお客様各自のオンプレミスにてご用意下さい、AWSは対向するインターフェースを提供します、というもの ≫CodeZine ≫Developers.IO

データベース

ネットワーキング & コンテンツ配信

移行

  • Application Discovery Service
    オンプレミスサーバの構成管理情報を収集する ≫公式

  • Database Migration Service (DMS)
    RDBをオンプレミスからAWSへ乗り換えるときに使う支援ツール

  • Server Migration Service (SMS)
    サーバをオンプレミスからAWSへ乗り換えるときに使う支援ツール

開発者用ツール

  • CodeCommit
    GitHubみたいなやつ

  • CodeBuild
    従量課金制ビルド

  • CodeDeploy
    コードデプロイ

  • CodePipeline
    Continuous Integration (CI) オーケストレーション。ビルド→デプロイの自動実行フロー定義。

  • AWS X-Ray
    分散アプリケーションのトレース ≫Serverworks

管理ツール

セキュリティ、アイデンティティ、コンプライアンス

  • AWS Identity and Access Management (IAM)
    AWSの認証、権限管理単位 ≫Developers.IO

  • Inspector
    脆弱性検出 ≫公式

  • Certificate Manager
    X.509証明書の管理 ≫公式

  • AWS Cloud Hardware Security Module (HSM)
    秘密鍵の保管(暗号、署名) ≫公式

  • AWS Directory Service
    Active Directory ≫Developers.IO

  • AWS Web Application Firewall (WAF)
    ファイアーウォール ≫公式

  • AWS Shield
    DDoS対策 ≫公式

分析

人工知能

IoT

ゲーム開発

モバイルサービス

  • Mobile Hub
    AWSのいろんなmBaaS系サービスを統合的に使えるコンソール画面 ≫Qiita

  • Cognito
    ソーシャル認証+データ同期。FacebookログインとかTwitterログインとか ≫Cookpad

  • AWS Device Farm
    テスト環境。Android, iOSの実機にリモートアクセスしてテストができる ≫公式

  • Mobile Analytics
    アプリの使用データの測定、追跡、分析 ≫公式 ≫Developers.IO

  • Pinpoint
    プッシュ ≫Qiita

アプリケーションサービス

  • Step Functions
    フローチャートみたいなビジュアルワークフローを画面上に描いて分散アプリケーションを構築する、というもの ≫公式

  • Amazon Simple Workflow (SWF)
    旧世代サービス。現在はStep Functionsを推奨 ≫公式

  • API Gateway
    HTTP API化 ≫公式

  • Elastic Transcoder
    動画、音声のフォーマット変換。つんでれんこaaSみたいなヤツ ≫Serverworks

メッセージング

  • Amazon Simple Queue Service (SQS)
    メッセージキュー ≫公式

  • Amazon Simple Notification Service (SNS)
    プッシュ ≫公式

  • Amazon Simple Email Service (SES)
    E-mail配信。メルマガとか ≫公式

ビジネスの生産性

デスクトップとアプリケーションのストリーミング

  • Amazon WorkSpaces
    仮想デスクトップ ≫impress

  • Amazon WorkSpaces Application Manager (WAM)
    Amazon WorkSpaces端末にアプリを配信するツール ≫serverworks

  • AppStream 2.0
    Citrix XenAppみたいなやつ ≫Developers.IO

参考文献

AWS ドキュメント
https://aws.amazon.com/jp/documentation/

AWS re:Invent 2016 発表サービスを三行でまとめる
http://qiita.com/szk3/items/a642c62ef56eadd4a12c

続きを読む

ECSのチュートリアル – コンテナ運用を現実のものにする

皆さんこんにちは

以前にコンテナの何がいいのかを説明する資料を作りました。
「やっぱりコンテナ運用しか無いな」
「コンテナを使わざるを得ない」
「とりあえずdocker入れとけばいいや」
という声が聞こえてきそうです(幻聴)

一方で、コンテナで運用するとなると、新たなる監視体制が必要となります。
例えば、Dockerのコンテナが突然停止した場合、その状況を検知し、新しくコンテナを立ち上げる必要があります。
また、そもそもコンテナが乗っている環境をスケールする必要が出たりして、その場合は新しくスケールされた環境でもコンテナを立ち上げなければならないし、その設定をサーバに書かなければならないけど、そうなると結局コンテナが乗っているサーバがブラックボックス化したりするわけで、なんとかうまく回そうとすると、それなりに気を配ることが多くなってきます。

最近はlaradockを使用する開発者が増えてきた(ような気がする)ので、コンテナ運用で幸せになるためにECSを使ってみた記録をチュートリアル形式で再現しておきます。

え?備忘録?ははは

ECSとは

ECSはAWSが提供しているコンテナ運用サービスです。
ECSの概念のちょっとしたものは以前に書いたECSで困ったときに読むための俺的Q&Aに書いてありますが、今回はECSでたてるWEBサーバに絞って超かんたんなポンチ絵を作ってみました。

ecs.png

あまりややこしいところはなさそうですが、簡単に見ていきましょう。

クラスター、コンテナインスタンス

コンテナインスタンスはdockerのコンテナのホストになるインスタンスで、クラスターはこのコンテナインスタンスの群れ( オートスケーリンググループ )です。
コンテナインスタンスにはdockerとコンテナの状態を監視してこちらに伝えてくれるエージェントが入っています。
コンテナ運用時には、個々のインスタンスには興味が無いので、「クラスター」と一括りにしてしまいます。

タスク、ECR

タスクはアプリケーションのまとまりを一つ以上のコンテナ動作で定義します。
今回の例ではnginxを動かすコンテナとphp-fpmを動かすコンテナのふたつで、webサーバのタスクを定義していて、nginxが外部からのリクエストを受け付け、php-fpmに渡しています。
コンテナを定義するイメージはECRというAWSが提供しているプライベートなdockerイメージのレジストリから取得しています。

サービス

サービスは3つの仕事をします。
1つ目はタスクが現在必要とされている個数を保っているかを監視し、その数を下回っていた場合、タスクを立ち上げて必要数を保つことです。
2つ目は現在の負荷状況を鑑みて、タスクを増減させます。この設定はCloudWatchのアラーム機能と連携させます。
3つ目はtarget groupと立ち上がったタスクを紐付けることです。

また、タスクの配置を各AZに均等に配置したりと、よく気配りのきくやつです。

ALB、target group

ALBはロードバランサーです。昔のクラシックなELBと違って、間にtarget groupと言うものを設置して、細かくルーティングできるようになっています。
target groupは実際のサーバへの振り分けを実施し、ヘルスチェックもします。

今回はこれらを利用して簡単なWEBサーバを運用できる状況に持っていきましょう

コンテナの準備

とりあえずコンテナを作る

運用したくとも、そのもととなるコンテナイメージがないと何も始まりません。
そこで、例によってLaraDockを利用してlaravelアプリを作ってみましょう。
プロジェクトの作成はここを見てもらうとして(ダイマ)、LaraDockとほぼ同じ動作をしてもらうために、Dockerfileをコピーしちゃいましょう。

$ cp -r ../laradock/nginx ./
$ cp -r ../laradock/php-fpm ./

これでDockerfileと設定ファイルをコピーできます。

今回、コンテナの中は完全に固定化…つまり、スクリプトを内部に含ませてしまうため、各Dockerfileの後部に

COPY ./ /var/www/

これを追加しておきます
また、nginxのdefaultのコンフィグファイルも必要なので、nginxのDockerfileには

COPY nginx/sites/default.conf /etc/nginx/conf.d/

も追加しておきます。
なにはともあれ、イメージを作成してみましょう。

$ cp nginx/Dockerfile ./ && docker build -t niisan/nginx .
$ cp php-fpm/Dockerfile-70 ./Dockerfile && docker build -t niisan/php-fpm .

これでイメージが作られているので、試しにサーバを立ててみましょう。

$ docker run -d --name php-fpm niisan/php-fpm
$ docker run -d --link php-fpm:php-fpm -p 80:80  niisan/nginx
$ curl 127.0.0.1
<!DOCTYPE html>
<html lang="en">
    <head>
...

うまく動いているようです。

コンテナをアップロードする

次にECRにコンテナをアップロードするのですが、初めての場合、ちょっとビビります。
けばけばしいタイトルとともに「今すぐ始める」ボタンが登場し、その次のページで妙な選択肢を迫ってきます。

ECSを始めることを。。。強いられているんだ!

(こういうところ、あまり好きじゃないです)
ここではとりあえず、「Amazon ECR によりコンテナイメージをセキュアに保存する」だけを選択しておきます。
すると、リポジトリの作成用のウイザードが迫ってきます。

ECR

とりあえずリポジトリ名にnginxと入れて次のステップへ
今度はECRにイメージをアップロードする手順が出てきますので、この通りにやってみます。
前回の記事でも言及したように、ここだけはAWS CLIを使用する必要がありますので、頑張って入れておきましょう。

$ aws ecr get-login

これで出てくるログインコマンドをコマンドラインにコピーして実行すればログイン完了です。
既にイメージは作ってあるので、タグを張り替え、アップロードしましょう。

$ docker tag niisan/nginx:latest **************.ecr.ap-northeast-1.amazonaws.com/nginx:latest
$ docker push *****************.ecr.ap-northeast-1.amazonaws.com/nginx:latest

これで完了です。
手順ページの完了ボタンを押すと、いまアップロードしたイメージのレジストリができていることを確認できます。

そのまま、左のバーにあるリポジトリを選択し、同じようにphp-fpmもアップロードしてみましょう。
1分もかからず終わるでしょう。

ECR登録済み

デプロイ

タスクを定義する

コンテナのイメージもアップロードできたことですし、早速タスクを定義していきましょう。
と言っても、今回の場合、内容は異様なほど簡単ですんで、すぐに終わるでしょう。

タスクの名前は適当に決めていいと思います。(今回は webtest という名前にしました。)
早速コンテナを入れていきましょう。まずはphp-fpmから行きます。「コンテナを追加」ボタンを押すと次のモーダルが出てきます。

タスクを設定

php-fpmの設定はこれだけで十分でしょう。
次にnginxを設定します。基本的にはphp-fpmと同様にイメージと名前を設定するわけですが、二つほど、違う設定があります。

ポートフォワード

まずはポートの設定です。
nginxは外部から接続できなければならないので、ホストからポートフォワードするように設定しました。

内部リンク

もう一つはphp-fpmへのリンクです。
これで、タスクの定義は完了しました。

クラスターを作る

タスクを作ったので、こいつを動かしてみましょう。
こいつを動かすためには、動作環境が必要となりますが、それがクラスターです。
早速クラスターを作っていきましょう。

クラスターの作成

せこいですが、コンテナが動作するインスタンスは一番ちっちゃいやつを使います。
次にネットワーキングの設定ですが、これはデフォルトのvpcを使ってお茶を濁しておきます。

商用環境では独自のVPC使おう

では作成しましょう。

クラスター作成完了

クラスターが出来上がりました。

作ったクラスターを選択して、早速タスクを動かしてみましょう。
クラスター名を選択し、タスクタブからタスクの開始ボタンを押すと、動作するタスクを選択する画面になります。

タスクの実行

では動作してみましょう。
しばらくすると、次のような状態になっていると思います。

動いているっぽい

どうやら動いているようです。
ECSインスタンスからコンテナのホストになっているインスタンスを参照して、public dnsを調べましょう。
それをブラウザにはっつけると、Laravelのページが表示されます。
Webアプリのデプロイ完了
うまくいきました!

サービスを使う

とりあえずデプロイできて、動作も確認できました。
とはいえ、これを運用するのはまだまだ厳しいところです。

  • タスクが死んだ場合の復旧をどうすればいいのか
  • コンテナを更新したときのデプロイ方法はどうなるのか
  • コンテナのスケーリングはどうするのか

この辺をサービスに解決してもらうとしましょう。

タスクを停止してみる

とりあえず、タスクを停止します。クラスターのタスクタブで、先ほど動かしたタスクを落とします。
当然ですが、落としたタスクは復活したりしないので、この時点で先に動かしたWEBサーバはダウン状態となります。

イメージを更新する

これからロードバランサーを使用するに当たり、ヘルスチェック用のルーティングをします。
今回はlaravel5.4を使う(使っちゃってる)ので次のようなroutingを用意します。

routes/web.php
Route::get('/healthcheck', function () {
    return ['health'=>'ok'];
});

この状態で、Dockerのイメージビルドを再実行します。
コンテナを再度動かすと次のような挙動を示します。

$ docker run -d --name php-fpm niisan/php-fpm
$ docker run -d --link php-fpm:php-fpm -p 80:80  niisan/nginx
curl 127.0.0.1/healthcheck
{"health":"ok"}

これをECRに再アップロードしておきます。

タスクを更新する

次に、タスクを更新していきます。
タスクはバージョン管理されていて、タスクはの更新は正確には新しいリビジョンを作るということになります。

例えば、今動いているリビジョンのタスクを新しいリビジョンのタスクに置き換えることで、サーバを更新したことと同じ状況にできますし、いざ新しいリビジョンが障害を起こすようなものであった場合は古いリビジョンに戻してロールバックすることもできます。

タスク定義->webtestでタスク定義のリビジョン一覧が現れます。今は一個しか無いと思うので、それを選択し、新しいリビジョンの作成ボタンを押しましょう。
今回の変更はnginxのポートの部分になります。

ダイナミックポート

なんとホストポートが空欄になっていますが、これがダイナミックポートという仕組みになります。
これはコンテナインスタンスで開いているポートを選んで、nginxコンテナの80ポートに繋げる仕組みで、ALB + target groupと組み合わせるときに使用します。

ALBを用意する

ひとまずコンテナの調整はこの辺にしておいて、ロードバランサーの設定を始めましょう。
まず、サービス -> EC2 -> サイドバーのロードバランサーを選択し、ロードバランサーを作成ボタンを押しましょう。
ALBの設定
Application load balancer を選択して、次へを押すとロードバランサーの設定に移ります。
今回はデフォルトのVPCを使うので、名前以外は全部デフォルトで問題なしです。
サブネットも二つあると思うので、両方共チェックしておきましょう。
一応、プロトコルはHTTPにしておきましょう。
セキュリティの警告は、今回は別に秘密通信でもないので、必要ないです。

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

セキュリティグループはとにかく設定しておきましょう。
覚えやすい名前にしておくと、後で便利かもしれません。

空のターゲットグループ

ターゲットの登録画面になりますが、今はターゲットがないので、そのまま次へで、作成を完了させます。
これで、とりあえずロードバランサーの作成が完了しました。

最後に、ロードバランサーのセキュリティグループからコンテナインスタンスのセキュリティグループへの通信経路を確保しておきましょう。
コンテナインスタンスのセキュリティグループ( EC2ContainerService-*** )のinboundを編集しておきましょう。

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

ロードバランサーからのinboundで全てのtcpを選択しておきましょう。
ダイナミックポートを使うと、開いているポートを使うのでどのポートが割り当てられるかわかりません。

サービスの作成

ここまで来てようやくサービスの作成が初められます。
再びEC2 Container Serviceから先程作成したクラスターを選択し、サービスを選択します。
作成ボタンがあるので、これを押しましょう。
サービス作成
まず、タスクはWebtestを選ぶわけですが、ここではリビジョンを含めて指定することになります。
サービス名は・・・とりあえずタスクのファミリー名と同じにしています。
画面下部に行くとELBを設定するボタンがあるので押してみましょう。
スクリーンショット 2017-03-18 22.39.45.png
するとこんな画面になるので、さっき作ったALBを選択し、負荷分散用のコンテナがnginxで、ポートが0:80になっていることを確認したら、ELBへの追加ボタンを押しましょう
スクリーンショット 2017-03-18 22.41.36.png
するとこんな画面になるのでターゲットグループを先程追加したものに変更して保存を押しましょう。

すると、サービスにALBの情報が登録されるので、この状態でサービスを作成しましょう。

スクリーンショット 2017-03-18 23.41.08.png

クラスターの状態がこのようになれば、成功です。
あとは、ロードバランサーのDNSにアクセスして、
Webアプリのデプロイ完了
これが出れば成功です。

まとめ

実はこれ書き始めたのって随分と前なのですが、途中でコンテナ運用の何がいいのかを説明するための資料作りしていたら、時間が立ってしまったのです。
まあ、ECSって使ってみるとすごく楽で、更にスケーリングしたり、他のサービスと相乗りさせたりマイクロサービス化させたりがすごく簡単にできるのですが、Webアプリを動かすに当たっては、わりとハマりどころが多いので、一貫したチュートリアル形式のものを作ってみました。
特にハマりどころなのは、ALB -> コンテナインスタンスにおけるセキュリティグループの設定だと思います。
考えてみれば当たり前なのですが、まあ、忘れることが多いです。
とはいえ、そんなところを超えてしまえば、後はコンテナで簡単に運用できる環境を量産できるので、皆さん是非とも使ってみてください。

今回はそんなところです。

参考

コンテナに挫折したあなたへ – 超わかりやすい発表資料です!師匠と呼びたい

続きを読む