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

続きを読む

[JAWS-UG CLI] Lambda #25 StepFunctions用関数 (FailFunction)

前提条件

Lambdaへの権限

Lambdaに対してフル権限があること。

AWS CLI

以下のバージョンで動作確認済

  • AWS CLI 1.10.60
コマンド
aws --version

結果(例):

  aws-cli/1.11.34 Python/2.7.10 Darwin/15.6.0 botocore/1.4.91

バージョンが古い場合は最新版に更新しましょう。

コマンド
sudo -H pip install -U awscli

IAM Role

‘lambdaBasicExecution’ロールが存在すること。

0. 準備

まず変数の確認をします。

変数の確認
cat << ETX

        AWS_DEFAULT_PROFILE: (0.1) ${AWS_DEFAULT_PROFILE}
        AWS_DEFAULT_REGION:  (0.2) ${AWS_DEFAULT_REGION}
        IAM_ROLE_ARN:        (0.3) ${IAM_ROLE_ARN}

ETX

結果(例):

  AWS_DEFAULT_PROFILE: (0.1) lambdaFull-prjz-mbp13
  AWS_DEFAULT_REGION:  (0.2) ap-northeast-1
  IAM_ROLE_ARN:        (0.3) arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution

変数が入っていない、適切でない場合は、それぞれの手順番号について作業を
行います。

0.1. プロファイルの指定

プロファイルの一覧を確認します。

コマンド
cat ~/.aws/credentials \
       | grep '\[' \
       | sed 's/\[//g' | sed 's/\]//g'

結果(例):

  iamFull-prjz-mbpr13
  lambdaFull-prjz-mbp13
変数の設定
export AWS_DEFAULT_PROFILE='lambdaFull-prjz-mbp13'

0.2. リージョンの指定

変数の設定
export AWS_DEFAULT_REGION='ap-northeast-1'

0.3. IAMロールの指定

変数の設定
IAM_ROLE_NAME='lambdaBasicExecution'
コマンド
aws iam get-role \
         --role-name ${IAM_ROLE_NAME}

結果(例):

  {
      "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        },
        "RoleId": "AROAXXXXXXXXXXXXXXXXX",
        "CreateDate": "2017-03-19T01:23:45Z",
        "RoleName": "lambdaBasicExecution",
        "Path": "/",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution"
      }
  }
コマンド
IAM_ROLE_ARN=$( \
        aws iam get-role \
          --role-name ${IAM_ROLE_NAME} \
          --query 'Role.Arn' \
          --output text \
) \
        && echo ${IAM_ROLE_ARN}

結果(例):

  arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution

1. 事前作業

1.1. Lambda関数名の決定

変数の設定
LAMBDA_FUNC_NAME='FailFunction'

同名のLambda関数の不存在確認

コマンド
aws lambda get-function \
        --function-name ${LAMBDA_FUNC_NAME}

結果(例):

  A client error (ResourceNotFoundException) occurred when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:FailFunction

1.2. Lambda関数

変数の設定
FILE_LAMBDA_FUNC='FailFunction.js'
コマンド
cat << EOF > ${FILE_LAMBDA_FUNC}
exports.handler = function(event, context, callback) {
   function AccountAlreadyExistsError(message) {
       this.name = "AccountAlreadyExistsError";
       this.message = message;
   }
   AccountAlreadyExistsError.prototype = new Error();

   const error = new AccountAlreadyExistsError("Account is in use!");
   callback(error);
};
EOF
  cat ${FILE_LAMBDA_FUNC}

結果(例):

  exports.handler = function(event, context, callback) {
     function AccountAlreadyExistsError(message) {
         this.name = "AccountAlreadyExistsError";
         this.message = message;
     }
     AccountAlreadyExistsError.prototype = new Error();

     const error = new AccountAlreadyExistsError("Account is in use!");
     callback(error);
  };
コマンド
zip ${LAMBDA_FUNC_NAME}.zip ${FILE_LAMBDA_FUNC}

結果:

  adding: FailFunction.js (deflated 43%)

2. Lambda関数の作成

変数の設定
LAMBDA_FUNC_DESC='Always generate an error.'
LAMBDA_RUNTIME='nodejs4.3'
LAMBDA_HANDLER="${LAMBDA_FUNC_NAME}.handler"
FILE_LAMBDA_ZIP="${LAMBDA_FUNC_NAME}.zip"
変数の確認
cat << ETX

        LAMBDA_FUNC_NAME:  ${LAMBDA_FUNC_NAME}
        LAMBDA_FUNC_DESC: "${LAMBDA_FUNC_DESC}"
        LAMBDA_RUNTIME:    ${LAMBDA_RUNTIME}
        FILE_LAMBDA_ZIP    ${FILE_LAMBDA_ZIP}
        IAM_ROLE_ARN:      ${IAM_ROLE_ARN}
        LAMBDA_HANDLER:    ${LAMBDA_HANDLER}

ETX
コマンド
aws lambda create-function \
        --function-name ${LAMBDA_FUNC_NAME} \
        --description "${LAMBDA_FUNC_DESC}" \
        --zip-file fileb://${FILE_LAMBDA_ZIP} \
        --runtime ${LAMBDA_RUNTIME} \
        --role ${IAM_ROLE_ARN} \
        --handler ${LAMBDA_HANDLER}

結果(例):

  {
    "CodeSha256": "uW+ZzaP1iDzaUfhFUyed0CdaOcSxbcsFd7yJXjHbWX8=",
    "FunctionName": "FailFunction",
    "CodeSize": 353,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:FailFunction",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
    "Timeout": 3,
    "LastModified": "2017-03-19T01:23:45.678+0000",
    "Handler": "FailFunction.handler",
    "Runtime": "nodejs4.3",
    "Description": "Always generate an error."
  }
コマンド
aws lambda get-function \
        --function-name ${LAMBDA_FUNC_NAME}

結果(例):

  {
    "Code": {
      "RepositoryType": "S3",
      "Location": "https://awslambda-ap-ne-1-tasks.s3-ap-northeast-1.amazonaws.com/snapshots/XXXXXXXXXXXX/FailFunction-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?x-amz-security-token=AQxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&AWSAccessKeyId=ASIAXXXXXXXXXXXXXXXX&Expires=xxxxxxxxxx&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  "Configuration": {
      "Version": "$LATEST",
      "CodeSha256": "uW+ZzaP1iDzaUfhFUyed0CdaOcSxbcsFd7yJXjHbWX8=",
      "FunctionName": "FailFunction",
      "MemorySize": 128,
      "CodeSize": 353,
      "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:FailFunction",
      "Handler": "FailFunction.handler",
      "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
      "Timeout": 3,
      "LastModified": "2017-03-19T01:23:45.678+0000",
      "Runtime": "nodejs4.3",
      "Description": "Always generate an error."
    }
  }
コマンド
aws lambda get-function-configuration \
        --function-name ${LAMBDA_FUNC_NAME}

結果(例):

  {
    "CodeSha256": "uW+ZzaP1iDzaUfhFUyed0CdaOcSxbcsFd7yJXjHbWX8=",
    "FunctionName": "FailFunction",
    "CodeSize": 353,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:FailFunction",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
    "Timeout": 3,
    "LastModified": "2017-03-19T01:23:45.678+0000",
    "Handler": "FailFunction.handler",
    "Runtime": "nodejs4.3",
    "Description": "Always generate an error."
  }

3. Lambda関数の動作確認

3.1. サンプルデータの作成

変数の設定
FILE_INPUT="${LAMBDA_FUNC_NAME}-data.json" \
          && echo ${FILE_INPUT}

サンプルデータ:

  cat << EOF > ${FILE_INPUT}
  {
    "key3": "value3",
    "key2": "value2",
    "key1": "value1"
  }
  EOF

  cat ${FILE_INPUT}

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

コマンド
jsonlint -q ${FILE_INPUT}

エラーが出力されなければOKです。

3.2. lambda関数の手動実行

変数の設定
FILE_OUTPUT_LAMBDA="${LAMBDA_FUNC_NAME}-out.txt"
FILE_LOG_LAMBDA="${LAMBDA_FUNC_NAME}-$(date +%Y%m%d%H%M%S).log"
変数の確認
cat << ETX

        LAMBDA_FUNC_NAME:   ${LAMBDA_FUNC_NAME}
        FILE_INPUT:         ${FILE_INPUT}
        FILE_OUTPUT_LAMBDA: ${FILE_OUTPUT_LAMBDA}
        FILE_LOG_LAMBDA:    ${FILE_LOG_LAMBDA}

ETX
コマンド
aws lambda invoke \
        --function-name ${LAMBDA_FUNC_NAME} \
        --log-type Tail \
        --payload file://${FILE_INPUT} \
        ${FILE_OUTPUT_LAMBDA} \
        > ${FILE_LOG_LAMBDA}
コマンド
cat ${FILE_LOG_LAMBDA} \
        | jp.py 'StatusCode'

結果(例):

  200

3.3. lambda関数の実行結果の確認

コマンド
cat ${FILE_OUTPUT_LAMBDA}

結果:

  {"errorMessage":"Account is in use!","errorType":"AccountAlreadyExistsError","stackTrace":["exports.handler (/var/task/FailFunction.js:6:42)"]}

3.4. lambda関数のログの確認

コマンド
cat ${FILE_LOG_LAMBDA} \
        | jp.py 'LogResult' \
        | sed 's/\"//g' \
        | base64 --decode

結果(例):

  START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
  2017-03-19T01:23:45.678Z     xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx    {"errorMessage":"Account is in use!","errorType":"AccountAlreadyExistsError","stackTrace":["exports.handler (/var/task/FailFunction.js:6:42)"]}
  REPORT RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx      Duration: 41.14 ms      Billed Duration: 100 ms         Memory Size: 128 MB        Max Memory Used: 10 MB

完了

続きを読む

[JAWS-UG CLI] IAM #75 IAMロールのポリシー追加 (AWSLambdaRole / AWS)

まず最初に、 今回実行するLambda関数に必要な権限を付与するためのIAMロールを作成していきます。

Lambda関数を実行する上で、最低限必要な権限となります。

前提条件

IAMへの権限

IAMに対してフル権限があること。

AWS CLI

以下のバージョンで動作確認済

  • AWS CLI 1.10.60
コマンド
aws --version

結果(例):

  aws-cli/1.11.34 Python/2.7.10 Darwin/15.6.0 botocore/1.4.91

バージョンが古い場合は最新版に更新しましょう。

コマンド
sudo -H pip install -U awscli

0. 準備

0.1. 変数の確認

プロファイルが想定のものになっていることを確認します。

変数の確認
aws configure list

結果(例):

        Name                    Value             Type    Location
        ----                    -----             ----    --------
     profile       iamFull-prjz-mbp13        env    AWS_DEFAULT_PROFILE
  access_key     ****************XXXX shared-credentials-file
  secret_key     ****************XXXX shared-credentials-file
      region        ap-northeast-1        env    AWS_DEFAULT_REGION

今回は、IAMでの作業となるため、リージョンはどこになっていても影響あり
ません。

0.2. IAMロール名の指定

ポリシーをアタッチするIAMロールを指定します。

変数の設定
IAM_ROLE_NAME="StepFunctionsRole-${AWS_DEFAULT_REGION}" \
  && echo ${IAM_ROLE_NAME}

IAMロールの内容を確認しましょう。

コマンド
aws iam get-role \
         --role-name ${IAM_ROLE_NAME}

結果(例):

  {
      "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "states.${AWS_DEFAULT_REGION}.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        },
        "RoleId": "AROAXXXXXXXXXXXXXXXXX",
        "CreateDate": "2017-03-18T01:23:45Z",
        "RoleName": "StepFunctionsRole-ap-northeast-1",
        "Path": "/",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/StepFunctionsRole-ap-northeast-1"
      }
  }

1. 事前作業

次に、このIAMロールにIAMポリシーを適用して、このIAMロールを利用するこ
とで得られる権限を決定します。

1.1. IAMロールに適用するIAMポリシーの決定

今回のStepFunctionsを実行する上で必要となる権限については、すでにAWS管
理ポリシーで”AWSLambdaRole’という名前で提供されています。

まず、’Lambda’というキーワードでAWS管理ポリシーから検索してみましょう

変数の設定
SEARCH_KEYWORD='Lambda'
コマンド
aws iam list-policies \
        --scope AWS \
        --max-items 1000 \
        --query "Policies[?contains(PolicyName, \`${SEARCH_KEYWORD}\`)].PolicyName"

結果(例):

  [
    "AWSLambdaFullAccess",
    "AWSLambdaDynamoDBExecutionRole",
    "AWSLambdaExecute",
    "AWSLambdaKinesisExecutionRole",
    "AWSLambdaReadOnlyAccess",
    "AWSLambdaBasicExecutionRole",
    "AWSLambdaInvocation-DynamoDB",
    "AWSLambdaVPCAccessExecutionRole",
    "AWSLambdaRole",
    "AWSLambdaENIManagementAccess"
  ]

利用するIAMポリシーを決めます。

変数の設定
IAM_POLICY_NAME='AWSLambdaRole'

次に、ポリシーを特定するためのAWSリソース識別名(ARN)を取得します。

変数の設定
IAM_POLICY_ARN=$( \
        aws iam list-policies \
          --max-items 1000 \
          --query "Policies[?PolicyName==\`${IAM_POLICY_NAME}\`].Arn" \
          --output text \
) \
        && echo "${IAM_POLICY_ARN}"

結果(例):

  arn:aws:iam::aws:policy/service-role/AWSLambdaRole

ポリシーの内容を見るために、そのポリシーの最新バージョン名を取得します

コマンド
IAM_POLICY_VERSION=$( \
        aws iam list-policies \
          --max-items 1000 \
          --query "Policies[?PolicyName==\`${IAM_POLICY_NAME}\`].DefaultVersionId" \
          --output text \
) \
        && echo ${IAM_POLICY_VERSION}

結果(例)

  v1

ポリシーの最新バージョンの内容を見てみましょう。

コマンド
aws iam get-policy-version \
        --policy-arn ${IAM_POLICY_ARN} \
        --version-id ${IAM_POLICY_VERSION}

結果(例):

  {
      "PolicyVersion": {
          "CreateDate": "2015-02-06T18:41:28Z", 
          "VersionId": "v1", 
          "Document": {
              "Version": "2012-10-17", 
              "Statement": [
                  {
                      "Action": [
                          "lambda:InvokeFunction"
                      ], 
                      "Resource": [
                          "*"
                      ], 
                      "Effect": "Allow"
                  }
              ]
          }, 
          "IsDefaultVersion": true
      }
  }

以下の権限を許可されていることがわかります。

  • lambda:InvokeFunction

これは、Lambda関数を実行するために必要な権限です。

1.2. IAMポリシーの確認

IAMロールに適用されているIAMポリシーを確認してみましょう。

コマンド
aws iam list-attached-role-policies \
        --role-name ${IAM_ROLE_NAME}

結果:

  {
      "AttachedPolicies": [], 
  }

作成直後のIAMロールには、適用されているIAMポリシーはありません。

2. IAMポリシーの適用

IAMロールにIAMポリシーを適用します。

変数の確認
cat << ETX

        IAM_ROLE_NAME:  ${IAM_ROLE_NAME}
        IAM_POLICY_ARN: ${IAM_POLICY_ARN}

ETX
コマンド
aws iam attach-role-policy \
        --role-name ${IAM_ROLE_NAME} \
        --policy-arn ${IAM_POLICY_ARN}

結果:

  (戻り値なし)

3. 事後作業

ポリシーの確認

IAMロールに適用されているIAMポリシーを再度確認してみましょう。

コマンド
aws iam list-attached-role-policies \
        --role-name ${IAM_ROLE_NAME}

結果(例):

  {
    "AttachedPolicies": [
      {
          "PolicyName": "AWSLambdaRole",
          "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaRole"
      }
    ]
  }

完了

以上で、今回実行するStepFunctionsに必要なIAMロールの作成は完了です。

続きを読む

[JAWS-UG CLI] Lambda #24 StepFunctions用関数 (Hello World)

前提条件

Lambdaへの権限

Lambdaに対してフル権限があること。

AWS CLI

以下のバージョンで動作確認済

  • AWS CLI 1.10.63
コマンド
aws --version

結果(例):

  aws-cli/1.11.63 Python/2.7.10 Darwin/15.6.0 botocore/1.5.26

バージョンが古い場合は最新版に更新しましょう。

コマンド
sudo -H pip install -U awscli

IAM Role

‘lambdaBasicExecution’ロールが存在すること。

0. 準備

まず変数の確認をします。

変数の確認
cat << ETX

        AWS_DEFAULT_PROFILE: (0.1) ${AWS_DEFAULT_PROFILE}
        AWS_DEFAULT_REGION:  (0.2) ${AWS_DEFAULT_REGION}
        IAM_ROLE_ARN:        (0.3) ${IAM_ROLE_ARN}

ETX

結果(例):

  AWS_DEFAULT_PROFILE: (0.1) lambdaFull-prjz-mbp13
  AWS_DEFAULT_REGION:  (0.2) ap-northeast-1
  IAM_ROLE_ARN:        (0.3) arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution

変数が入っていない、適切でない場合は、それぞれの手順番号について作業を
行います。

0.1. プロファイルの指定

プロファイルの一覧を確認します。

コマンド
cat ~/.aws/credentials \
       | grep '\[' \
       | sed 's/\[//g' | sed 's/\]//g'

結果(例):

  iamFull-prjz-mbpr13
  lambdaFull-prjz-mbp13
変数の設定
export AWS_DEFAULT_PROFILE='lambdaFull-prjz-mbp13'

0.2. リージョンの指定

変数の設定
export AWS_DEFAULT_REGION='ap-northeast-1'

0.3. IAMロールの指定

変数の設定
IAM_ROLE_NAME='lambdaBasicExecution'
コマンド
aws iam get-role \
         --role-name ${IAM_ROLE_NAME}

結果(例):

  {
      "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        },
        "RoleId": "AROAXXXXXXXXXXXXXXXXX",
        "CreateDate": "2017-03-18T01:23:45Z",
        "RoleName": "lambdaBasicExecution",
        "Path": "/",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution"
      }
  }
コマンド
IAM_ROLE_ARN=$( \
        aws iam get-role \
          --role-name ${IAM_ROLE_NAME} \
          --query 'Role.Arn' \
          --output text \
) \
        && echo ${IAM_ROLE_ARN}

結果(例):

  arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution

1. 事前作業

1.1. Lambda関数名の決定

変数の設定
LAMBDA_FUNC_NAME='HelloFunction'

同名のLambda関数の不存在確認

コマンド
aws lambda get-function \
        --function-name ${LAMBDA_FUNC_NAME}

結果(例):

  A client error (ResourceNotFoundException) occurred when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:HelloFunction

1.2. Lambda関数

変数の設定
FILE_LAMBDA_FUNC='HelloFunction.js'
コマンド
cat << EOF > ${FILE_LAMBDA_FUNC}
exports.handler = (event, context, callback) => {
    callback(null, "Hello, " + event.who + "!");
};
EOF

cat ${FILE_LAMBDA_FUNC}

結果(例):

  exports.handler = (event, context, callback) => {
      callback(null, "Hello, " + event.who + "!");
  };
コマンド
zip ${LAMBDA_FUNC_NAME}.zip ${FILE_LAMBDA_FUNC}

結果:

  adding: HelloFunction.js (deflated 43%)

2. Lambda関数の作成

変数の設定
LAMBDA_FUNC_DESC='Say Hello to someone'
LAMBDA_RUNTIME='nodejs4.3'
LAMBDA_HANDLER="${LAMBDA_FUNC_NAME}.handler"
FILE_LAMBDA_ZIP="${LAMBDA_FUNC_NAME}.zip"
変数の確認
cat << ETX

        LAMBDA_FUNC_NAME:  ${LAMBDA_FUNC_NAME}
        LAMBDA_FUNC_DESC: "${LAMBDA_FUNC_DESC}"
        LAMBDA_RUNTIME:    ${LAMBDA_RUNTIME}
        FILE_LAMBDA_ZIP    ${FILE_LAMBDA_ZIP}
        IAM_ROLE_ARN:      ${IAM_ROLE_ARN}
        LAMBDA_HANDLER:    ${LAMBDA_HANDLER}

ETX
コマンド
aws lambda create-function \
        --function-name ${LAMBDA_FUNC_NAME} \
        --description "${LAMBDA_FUNC_DESC}" \
        --zip-file fileb://${FILE_LAMBDA_ZIP} \
        --runtime ${LAMBDA_RUNTIME} \
        --role ${IAM_ROLE_ARN} \
        --handler ${LAMBDA_HANDLER}

結果(例):

  {
    "CodeSha256": "aNNHoAV+4ERLB3aO2i72HiCZzsDDNF/22V4HSO+7Gk0=",
    "FunctionName": "HelloFunction",
    "CodeSize": 270,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:HelloFunction",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
    "Timeout": 3,
    "LastModified": "2017-03-18T01:23:45.678+0000",
    "Handler": "HelloFunction.handler",
    "Runtime": "nodejs4.3",
    "Description": "Say Hello to someone"
  }
コマンド
aws lambda get-function \
        --function-name ${LAMBDA_FUNC_NAME}

結果(例):

  {
    "Code": {
      "RepositoryType": "S3",
      "Location": "https://awslambda-ap-ne-1-tasks.s3-ap-northeast-1.amazonaws.com/snapshots/XXXXXXXXXXXX/HelloFunction-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?x-amz-security-token=AQxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&AWSAccessKeyId=ASIAXXXXXXXXXXXXXXXX&Expires=xxxxxxxxxx&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  "Configuration": {
      "Version": "$LATEST",
      "CodeSha256": "aNNHoAV+4ERLB3aO2i72HiCZzsDDNF/22V4HSO+7Gk0=",
      "FunctionName": "HelloFunction",
      "MemorySize": 128,
      "CodeSize": 270,
      "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:HelloFunction",
      "Handler": "HelloFunction.handler",
      "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
      "Timeout": 3,
      "LastModified": "2017-03-18T01:23:45.678+0000",
      "Runtime": "nodejs4.3",
      "Description": "Say Hello to someone"
    }
  }
コマンド
aws lambda get-function-configuration \
        --function-name ${LAMBDA_FUNC_NAME}

結果(例):

  {
    "CodeSha256": "aNNHoAV+4ERLB3aO2i72HiCZzsDDNF/22V4HSO+7Gk0=",
    "FunctionName": "HelloFunction",
    "CodeSize": 270,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:HelloFunction",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaBasicExecution",
    "Timeout": 3,
    "LastModified": "2017-03-18T01:23:45.678+0000",
    "Handler": "HelloFunction.handler",
    "Runtime": "nodejs4.3",
    "Description": "Say Hello to someone"
  }

3. Lambda関数の動作確認

3.1. サンプルデータの作成

変数の設定
FILE_INPUT="${LAMBDA_FUNC_NAME}-data.json" \
          && echo ${FILE_INPUT}
サンプルデータ
cat << EOF > ${FILE_INPUT}
{
        "who": "AWS Step Functions"
}
EOF

cat ${FILE_INPUT}

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

コマンド
jsonlint -q ${FILE_INPUT}

エラーが出力されなければOKです。

3.2. lambda関数の手動実行

変数の設定
FILE_OUTPUT_LAMBDA="${LAMBDA_FUNC_NAME}-out.txt"
FILE_LOG_LAMBDA="${LAMBDA_FUNC_NAME}-$(date +%Y%m%d%H%M%S).log"
変数の確認
cat << ETX

        LAMBDA_FUNC_NAME:   ${LAMBDA_FUNC_NAME}
        FILE_INPUT:         ${FILE_INPUT}
        FILE_OUTPUT_LAMBDA: ${FILE_OUTPUT_LAMBDA}
        FILE_LOG_LAMBDA:    ${FILE_LOG_LAMBDA}

ETX
コマンド
aws lambda invoke \
        --function-name ${LAMBDA_FUNC_NAME} \
        --log-type Tail \
        --payload file://${FILE_INPUT} \
        ${FILE_OUTPUT_LAMBDA} \
        > ${FILE_LOG_LAMBDA}
コマンド
cat ${FILE_LOG_LAMBDA} \
        | jp.py 'StatusCode'

結果(例):

  200

3.3. lambda関数の実行結果の確認

コマンド
cat ${FILE_OUTPUT_LAMBDA}

結果:

  "Hello, AWS Step Functions!"

3.4. lambda関数のログの確認

コマンド
cat ${FILE_LOG_LAMBDA} \
        | jp.py 'LogResult' \
        | sed 's/\"//g' \
        | base64 --decode

結果(例):

  START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
  END RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  REPORT RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx      Duration: 2.08 ms       Billed Duration: 100 ms         Memory Size: 128 MB        Max Memory Used: 11 MB

完了

続きを読む

[JAWS-UG CLI] IAM #74 IAMロールの作成 (StepFunctionsRole)

前提条件

IAMへの権限

IAMに対してフル権限があること。

AWS CLIのバージョン

以下のバージョンで動作確認済

  • AWS CLI 1.11.63
コマンド
aws --version

結果(例):

  aws-cli/1.11.63 Python/2.7.10 Darwin/15.6.0 botocore/1.5.26

バージョンが古い場合は最新版に更新しましょう。

コマンド
sudo -H pip install -U awscli

0. 準備

0.1. リージョンの指定

変数の設定
export AWS_DEFAULT_REGION='ap-northeast-1'

0.2. プロファイルの確認

プロファイルが想定のものになっていることを確認します。

コマンド
aws configure list

結果(例)

        Name                    Value             Type    Location
        ----                    -----             ----    --------
     profile         iamFull-prjz-mbp13        env    AWS_DEFAULT_PROFILE
  access_key     ****************XXXX shared-credentials-file
  secret_key     ****************XXXX shared-credentials-file
      region                         ap-northeast-1  env    AWS_DEFAULT_REGION

AssumeRoleを利用している場合はprofileが ”と表示されます。 そ
れ以外のときにprofileが ” と表示される場合は、以下を実行して
ください。

変数の設定:

     export AWS_DEFAULT_PROFILE=<IAMユーザ名>

1. 事前作業

1.1. IAMロール名の決定

変数の設定
IAM_ROLE_NAME="StepFunctionsRole-${AWS_DEFAULT_REGION}" \
        && echo ${IAM_ROLE_NAME}

同じ名前のIAMロールが存在しないことを確認します。

コマンド
aws iam get-role \
         --role-name ${IAM_ROLE_NAME}

結果(例):

  An error occurred (NoSuchEntity) when calling the GetRole operation: Unknown

1.2. プリンシパルの決定

変数の設定
IAM_PRINCIPAL="states.${AWS_DEFAULT_REGION}.amazonaws.com" \
        && echo ${IAM_PRINCIPAL}

1.3. assumeロールポリシドキュメントの作成

変数の設定
FILE_INPUT="${IAM_ROLE_NAME}.json" \
        && echo ${FILE_INPUT}
変数の確認
cat << ETX

        FILE_INPUT:    ${FILE_INPUT}
        IAM_PRINCIPAL: ${IAM_PRINCIPAL}

ETX
コマンド
cat << EOF > ${FILE_INPUT}
{
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": "sts:AssumeRole",
            "Principal": {
              "Service": "${IAM_PRINCIPAL}"
            },
            "Effect": "Allow",
            "Sid": ""
          }
        ]
}
EOF

cat ${FILE_INPUT}

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

コマンド
jsonlint -q ${FILE_INPUT}

エラーが出力されなければOKです。

2. 本作業

IAMロールの作成

変数の確認
cat << ETX

        IAM_ROLE_NAME: ${IAM_ROLE_NAME}
        FILE_INPUT:    ${FILE_INPUT}

ETX
コマンド
aws iam create-role \
        --role-name ${IAM_ROLE_NAME} \
        --assume-role-policy-document file://${FILE_INPUT}

結果(例):

  {
     "Role": {
       "AssumeRolePolicyDocument": {
           "Version": "2012-10-17",
           "Statement": [
               {
                   "Action": "sts:AssumeRole",
                   "Sid": "",
                   "Effect": "Allow",
                   "Principal": {
                       "Service": "states.ap-northeast-1.amazonaws.com"
                   }
               }
           ]
       },
       "RoleId": "AROAXXXXXXXXXXXXXXXXX",
       "CreateDate": "2017-03-18T01:23:45.678Z",
       "RoleName": "StepFunctionsRole-ap-northeast-1",
       "Path": "/",
       "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/StepFunctionsRole-ap-northeast-1"
     }
   }

3. 事後作業

4.1. IAMロールの確認

コマンド
aws iam get-role \
         --role-name ${IAM_ROLE_NAME}

結果(例):

  {
      "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "states.ap-northeast-1.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        },
        "RoleId": "AROAXXXXXXXXXXXXXXXXX",
        "CreateDate": "2017-03-18T01:23:45Z",
        "RoleName": "StepFunctionsRole-ap-northeast-1",
        "Path": "/",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/StepFunctionsRole-ap-northeast-1"
      }
  }

完了

続きを読む

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

続きを読む

Terraformを使って GCP Pub/Sub から AWS Lambda へのPush通知の仕組みを作成した

先日作ったのでその覚書的なものです。
簡単に言うとこんな感じのものを作りました。

GKE上のDockerアプリ -> Stackdriver logging -> Pub/Sub -> Lambda -> Slack

作成の動機

概ね以下の背景です。

  • GCPをメインに、AWSも多少織り交ぜつつな感じのアプリケーションを作っている。
  • Stackdriverでアプリの監視を行っているがエラーログを検知したらSlackに通知する仕組みが欲しかった。
  • Stackdriver単体だとログ内容をSlackに通知できなさそうだった。
  • StackdriverはフィルタリングしたログをPub/Subへ流すことができるらしい。
  • Pub/Subはキューにデータが流れてきたときに指定したエンドポイントにPush通知できるらしい。

と、こんなかんじの前提があり Pub/Sub から lambda に流して、そこからさらにSlackに流せば実現できると目論んだ次第です。

LambdaにもPub/Subにも今回始めて触ったということもあってか結構迷走してしまいました。。

以降から本題となります。

Terraformに読み込ませるHCL

これがほとんど全てな感じですが、実際に使ったHCLをコメントを交えつつ添付しておきます。
varになっているところは適宜置換が必要かと思われますのでご注意下さい。

基本的にはTerraformサイト上のサンプルを組み合わせているだけです。
Terraformマジ便利ですね、最高です。
もっといろいろな知見がネット上に溜まってくれるとうれしいです。

data "aws_caller_identity" "self" {}

#### ここから API Gateway 周り####
resource "aws_api_gateway_rest_api" "alert_api" {
  name = "alerter"
}

resource "aws_api_gateway_method" "root_post_method" {
  rest_api_id   = "${aws_api_gateway_rest_api.alert_api.id}"
  resource_id   = "${aws_api_gateway_rest_api.alert_api.root_resource_id}"
  http_method   = "POST"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "alert_integration" {
  rest_api_id             = "${aws_api_gateway_rest_api.alert_api.id}"
  resource_id             = "${aws_api_gateway_rest_api.alert_api.root_resource_id}"
  http_method             = "${aws_api_gateway_method.root_post_method.http_method}"
  integration_http_method = "POST"
  type                    = "AWS"
  uri                     = "arn:aws:apigateway:${var.aws_region}:lambda:path/2015-03-31/functions/${aws_lambda_function.alerter.arn}/invocations"
}

resource "aws_api_gateway_method_response" "alert_200_response" {
  rest_api_id = "${aws_api_gateway_rest_api.alert_api.id}"
  resource_id = "${aws_api_gateway_rest_api.alert_api.root_resource_id}"
  http_method = "${aws_api_gateway_method.root_post_method.http_method}"
  status_code = "200"
}

resource "aws_api_gateway_integration_response" "alert_response" {
  rest_api_id = "${aws_api_gateway_rest_api.alert_api.id}"
  resource_id = "${aws_api_gateway_rest_api.alert_api.root_resource_id}"
  http_method = "${aws_api_gateway_method.root_post_method.http_method}"
  status_code = "${aws_api_gateway_method_response.alert_200_response.status_code}"
}

## stage名に使う名前をランダム文字列として生成する
resource "random_id" "secret_path" {
  keepers = {
    ami_id = "${aws_lambda_function.alerter.id}"
  }

  byte_length = 40
}

resource "aws_api_gateway_deployment" "alert_deployment" {
  depends_on = ["aws_api_gateway_method.root_post_method"]

  rest_api_id = "${aws_api_gateway_rest_api.alert_api.id}"
  stage_name  = "${random_id.secret_path.hex}"
}

resource "aws_api_gateway_domain_name" "alert" {
  domain_name = "${var.alerter_domain}"

  certificate_name        = "alert-ssl"
  certificate_body        = "${file("cert_path")}"
  certificate_chain       = "${file("chain_path")}"
  certificate_private_key = "${file("key_path")}"
}

resource "aws_api_gateway_base_path_mapping" "alert" {
  api_id      = "${aws_api_gateway_rest_api.alert_api.id}"
  domain_name = "${aws_api_gateway_domain_name.alert.domain_name}"
}

#### ここから IAM 周り####
resource "aws_iam_role" "lambda_alert_role" {
  name               = "lambda_alert_role"
  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      }
    }
  ]
}
POLICY
}

#### ここから Lambda 周り####
resource "aws_lambda_permission" "apigw_alerter" {
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = "${aws_lambda_function.alerter.arn}"
  principal     = "apigateway.amazonaws.com"

  source_arn    = "arn:aws:execute-api:${var.aws_region}:${data.aws_caller_identity.self.account_id}:${aws_api_gateway_rest_api.alert_api.id}/*/${aws_api_gateway_method.root_post_method.http_method}/"
}

resource "aws_lambda_function" "alerter" {
  filename         = "${var.path_to_lambda_source}"
  function_name    = "alerter"
  role             = "${aws_iam_role.lambda_alert_role.arn}"
  handler          = "index.handler"
  runtime          = "nodejs4.3"
  source_code_hash = "${base64sha256(file(${var.path_to_lambda_source}))}"
  environment {
    variables = {
      webhook_url = "${var.slack_webhook_url}"
      channel = "${var.slack_channel}"
    }
  }
}

#### ここから Route53 周り ####
resource "aws_route53_record" "alerter" {
  zone_id = "${aws_route53_zone.primary.zone_id}"

  name = "${aws_api_gateway_domain_name.alert.domain_name}"
  type = "A"

  alias {
    name    = "${aws_api_gateway_domain_name.alert.cloudfront_domain_name}"
    zone_id = "${aws_api_gateway_domain_name.alert.cloudfront_zone_id}"
    evaluate_target_health = true
  }
}

####
#### ここから GCP
####

#### ここから Pub/Sub 周り ####
resource "google_pubsub_topic" "log_alert_topic" {
  name = "log-alert-topic"
}

## CAUTION: 以下のリンクの「他のエンドポイントの登録」の手順を行っていないドメインをendpointに入れているとapplyに失敗します
## https://cloud.google.com/pubsub/advanced#push_endpoints
resource "google_pubsub_subscription" "aws_lambda_subscription" {
  name  = "default-subscription"
  topic = "log-alert-topic"

  ack_deadline_seconds = 10

  push_config {
    push_endpoint = "https://${aws_api_gateway_domain_name.alert.domain_name}/${aws_api_gateway_deployment.alert_deployment.stage_name}"

    attributes {
      x-goog-version = "v1"
    }
  }
}

ハマったところとか困ったところを何点か

これだけだと味気ないので、いくつかハマってしまった点を記録しておきます

Pub/Sub へ Push Subscriber への外部ドメインの登録

(おそらく)セキュリティ上の理由から Pub/Sub でPush通知するためにはそのエンドポイントの所有者であることを証明しないといけません。

HCLの方にもコメントをしていますが、AppEngine等のPub/Subと同じGCPプロジェクト内で管理されている以外のドメインを利用したい場合には事前に手作業でドメイン認証が必要となります。
このドメイン認証を行うために、 API Gateway をカスタムドメインで公開してやる必要がありました。
若干面倒ではありますが、認証できないエンドポイントにリクエストを投げられるようにしてしまうとDOS攻撃みたいなことにつかえてしまうのでこれは仕方がないのだと思います。

ともかく事前この作業をやっておかないと terraform apply に失敗してしまいます。
作業内容は別に難しいものではなく、大きく分けて以下の2ステップで完了します。

  • Google Search Console をつかってRoute53をイジイジしてドメイン認証

    • DNSのTXTレコードを使ったよくある感じの認証です。
  • GCP Console の API Manager から API Gateway に設定したカスタムドメインを登録

Pub/Sub と Lambda 間の認証

こちらは、単に自分の Pub/Sub と Lambda の知識不足な可能性があります。
HTTPヘッダーの中身をカスタムする等の方法による認証方法が見つけられずかなり悩んでしました。

結局、当座の対応としてHCL内で生成したランダム文字列を公開URLに含ませておき、これを知らないとAPIを叩けないという仕様にしています。
正直これについてはもっと上手な解決策があるような気がしてなりません。。

続きを読む

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 -> コンテナインスタンスにおけるセキュリティグループの設定だと思います。
考えてみれば当たり前なのですが、まあ、忘れることが多いです。
とはいえ、そんなところを超えてしまえば、後はコンテナで簡単に運用できる環境を量産できるので、皆さん是非とも使ってみてください。

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

参考

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

続きを読む