[stripe][AWS] Stripe の Event を CloudWatch custom metrics に記録

Stripe では、各種イベントを Webhook としてぶん投げてくれる仕組みがあります。
以前、その Webhook を Serverless Framework 使って API Gateway + Lambda で処理する記事書きました。
Stripe の Webhook を API Gateway で受け取って Lambda で処理する

それを応用して、Event Type ごとに CloudWatch のカスタムメトリクスに記録しておくとテンションあがるかなーと思って、こんなん書いてみました。

Stripe の Event を CloudWatch custom metrics に記録

Lambda で Webhook 受け取って CloudWatch にカスタムメトリクス登録

Lambda は node.js 6.10 でやりましょう。
ソースはこんな感じです。

handler.js
'use strict';

module.exports.incoming = (event, context, callback) => {
    const stripe = require('stripe')(process.env.STRIPE_API_KEY); // eslint-disable-line
    let response = {
        statusCode: 200,
        body: ''
    };

    try {
        // Parse Stripe Event
        const jsonData = JSON.parse(event.body); // https://stripe.com/docs/api#event_object

        // Verify the event by fetching it from Stripe
        console.log("Stripe Event: %j", jsonData); // eslint-disable-line
        stripe.events.retrieve(jsonData.id, (err, stripeEvent) => {
            const AWS = require('aws-sdk');
            const cloudwatch = new AWS.CloudWatch({apiVersion: '2010-08-01'});
            const eventType = stripeEvent.type ? stripeEvent.type : '';
            const params = {
                Namespace: process.env.NAMESPACE,
                MetricData: [
                    {
                        MetricName: eventType,
                        Dimensions: [{
                            Name: process.env.DIMENSION,
                            Value: process.env.STAGE
                        }],
                        Timestamp:  new Date,
                        Unit: 'Count',
                        Value: '1'
                    }
                ]
            };
            cloudwatch.putMetricData(params, (err) => {
                if (err) {
                    console.log('PutMetricData Error: %j', err);  // eslint-disable-line
                }
                response.body = JSON.stringify({
                    message: 'Stripe webhook incoming!',
                    stage: process.env.STAGE,
                    eventType: eventType
                });
                return callback(null,response);
            });
        });
    } catch (err) {
        response.statusCode = 501;
        response.body = JSON.stringify(err);
        callback(err,response);
    }
};

stripe.events.retrieve() で、送られてきたデータが本当に Stripe からのデータかどうかをチェックして、問題なければ CloudWatch のカスタムメトリクスに登録してる感じすね。

Serverless framework で API Gateway の endpoint 用意

API GateWay + Lambda の設定は苦行でしたが、Serverless framework を使用することで途端に簡単になりました。
こんな感じの serverless.yml を用意してやればいいです。

serverless.yml
service: put-stripe-event-to-custom-metrics

custom:
  stage:  ${opt:stage, self:provider.stage}
  logRetentionInDays:
    test: "14"         # stage[test]は14日
    live: "90"         # stage[live]は90日
    default: "3"       # stageが見つからなかったらこれにfallbackするために設定
  stripeAPIKey:
    test: "__STRIPE_TEST_API_SECRETKEY_HERE___"         # stage[test]用の Stripe API Key
    live: "__STRIPE_LIVE_API_SECRETKEY_HERE___"         # stage[live]用の Stripe API Key
    default: "__STRIPE_TEST_API_SECRETKEY_HERE___"      # stageが見つからなかったらこれにfallbackするために設定

provider:
  name: aws
  runtime: nodejs6.10
  stage: test
  region: us-east-1
  memorySize: 128
  timeout: 60

  iamRoleStatements:
    - Effect: Allow
      Action:
        - cloudwatch:PutMetricData
      Resource: "*"

  environment:
    STAGE: ${self:custom.stage}

# you can add packaging information here
package:
  include:
    - node_modules/**
  exclude:
    - .git/**
    - package.json

functions:
  incoming:
    handler: handler.incoming
    events:
      - http:
          path: stripe/incoming
          method: post
    environment:
      NAMESPACE: "___SERVICENAME__HERE__"
      DIMENSION: "Stripe"
      STRIPE_API_KEY: ${self:custom.stripeAPIKey.${self:custom.stage}, self:custom.stripeAPIKey.default}

resources:
  Resources:
    IncomingLogGroup:
      Properties:
        RetentionInDays: ${self:custom.logRetentionInDays.${self:custom.stage}, self:custom.logRetentionInDays.default}

serverless でのデフォルトに従うと、開発環境の stage は dev になりますが、ここは Stripe のお作法に従って開発環境は test、本番環境は live としましょうか。
environment として、以下の 4つを渡してますので、これを Lambda 内部では process.env.{environment name} で参照できます。

  • STAGE
  • NAMESPACE
  • DIMENSION
  • STRIPE_API_KEY

サンプルなので Stripe API KEY もそのまま渡してますが、必要なら暗号化するなりしてください。

environmentSTRIPE_API_KEY とか、Resources のロググループの期限とかは stage によって切り替えています。
この辺は @sawanoboly の以下の記事が参考になります。
Serverless Frameworkで、AWS Lambda function用に作られるCloudWatch Logsの期限を指定する

Deploy !

前準備

deploy したいところですが npm パッケージ stripe を内部で使用しているので、まずは npm install で stripe パッケージを取ってきましょう。

$ npm install stripe

いよいよデプロイ

もう、何も恐れる必要はありません。deploy してあげてください。

$ serverless deploy -v
 :
Serverless: Stack update finished...
Service Information
service: put-stripe-event-to-custom-metrics
stage: test
region: us-east-1
api keys:
  None
endpoints:
  POST - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/test/stripe/incoming
functions:
  incoming: put-stripe-event-to-custom-metrics-test-incoming

Stack Outputs
ServiceEndpoint: https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/test
ServerlessDeploymentBucketName: put-stripe-event-to-cust-serverlessdeploymentbuck-xxxxxxxxxxxx
IncomingLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:000000000000:function:put-stripe-event-to-custom-metrics-test-incoming:1

endpoint (https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/test/stripe/incoming) に表示される URL が API Gateway の endpoint です。
これを Stripe の Webhook 登録画面 で登録してあげればオッケーです。

本番環境( live )にデプロイする場合は、以下の感じで

$ serverless deploy -v -s live

やったー、CloudWatch のカスタムメトリクスに登録されてるよ!
Screen Shot 2017-06-13 at 17.17.25.png

これで、Stripe からのチャリンチャリンが CloudWatch で確認できるようになりました。
可視化大事ですね。

現場からは以上です。

続きを読む

AWSのec2にnginx+php7+mysqlを導入してwordpressを構築する準備をする

日本時間に設定

sudo cp /usr/share/zoneinfo/Japan /etc/localtime

ルートへ

sudo su -

nginxインストール

yum install -y nginx

php7インストール準備

rpm -Uvh https://mirror.webtatic.com/yum/el6/latest.rpm

php7インストール

yum install -y --enablerepo=webtatic-testing php70w php70w-devel php70w-fpm php70w-mysql php70w-mbstring php70w-pdo

nginx起動テスト

/etc/rc.d/init.d/nginx start

起動確認できたらnginx設定周り調整

nginx.conf

vi /etc/nginx/nginx.conf
nginx.conf
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}



http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   60;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.

    gzip_static       on;
    gzip              on;
    gzip_http_version 1.0;
    gzip_vary         on;
    gzip_comp_level   6;
    gzip_min_length 1024;
    gzip_types        text/plain text/xml text/css text/javascript
                      application/xhtml+xml application/xml
                      application/rss+xml application/atom_xml
                      application/javascript application/x-javascript
                      application/x-httpd-php application/json;
    gzip_disable      "MSIE [1-6].";

    proxy_cache_path  /var/cache/nginx levels=1:2
                      keys_zone=one:4m max_size=50m inactive=120m;
    proxy_temp_path   /var/tmp/nginx;
    proxy_cache_key   "$scheme://$host$request_uri";
    proxy_set_header  Host               $host;
    proxy_set_header  X-Real-IP          $remote_addr;
    proxy_set_header  X-Forwarded-Host   $host;
    proxy_set_header  X-Forwarded-Server $host;
    proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header  Accept-Encoding    "";
    proxy_connect_timeout 5;
    proxy_send_timeout 10;
    proxy_read_timeout 120;
    proxy_hide_header X-Pingback;
    proxy_hide_header X-Powered-By;
    proxy_hide_header Etag;
    proxy_hide_header Vary;
    proxy_cache_use_stale timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_cache_lock on;
    proxy_cache_lock_timeout 5s;
    index  index.html index.php index.htm;

    upstream backend {
        server unix:/var/run/nginx-backend.sock;
    }

    upstream phpfpm {
        server unix:/var/run/php-fpm.sock;
    }


    include /etc/nginx/conf.d/*.conf;


}        

リバースプロキシ用にバックエンド系

vi /etc/nginx/conf.d/backend.conf
backend.conf
server {
    listen unix:/var/run/nginx-backend.sock;
    server_name we-shirts.jp;
    root   /var/www/html;
    access_log  /var/log/nginx/backend.access.log;
    client_max_body_size 24M;
    fastcgi_connect_timeout 180;
    fastcgi_read_timeout 180;
    fastcgi_send_timeout 180;
    gzip              off;
    gzip_vary         off;

    location / {
#        ssi on;
#         index  index.html index.php index.htm;
        try_files /index.php?$args /index.php?q=$uri&$args;
#        try_files $uri $uri/ /index.php?$args /index.php?q=$uri&$args;
        index  index.php index.html index.htm;
#        try_files $uri $uri/ /index.php?$args /index.php?q=$uri&$args;
    }

    location ~ .(php|html)$ {
        try_files $uri =404;
        expires        off;
        fastcgi_pass   phpfpm;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_param  REMOTE_ADDR      $http_x_real_ip;
        fastcgi_pass_header "X-Accel-Redirect";
        fastcgi_pass_header "X-Accel-Buffering";
        fastcgi_pass_header "X-Accel-Charset";
        fastcgi_pass_header "X-Accel-Expires";
        fastcgi_pass_header "X-Accel-Limit-Rate";
    }

}

サーバ共通設定系

vi /etc/nginx/conf.d/hogehoge.conf
hogehoge.conf
server {
     listen       80;
     server_name  we-shirts.jp;
     root         /var/www/html;
     index        index.php index.html index.htm;
     charset      utf-8;

     client_max_body_size 100M;

     location = /favicon.ico { access_log off; log_not_found off; }
     location = /robots.txt { access_log off; log_not_found off; }
     location = /apple-touch-icon.png { access_log off; log_not_found off; }
     location ~ /. { deny all; access_log off; log_not_found off; }

     location ^~ /license.txt          { deny all; access_log off; log_not_found off; }
     location ^~ /readme.html          { deny all; access_log off; log_not_found off; }
     location ^~ /readme-ja.html       { deny all; access_log off; log_not_found off; }
     location ^~ /wp-activate.php      { deny all; access_log off; log_not_found off; }
     location ^~ /wp-blog-header.php   { deny all; access_log off; log_not_found off; }
     location ^~ /wp-cron.php          { deny all; access_log off; log_not_found off; }
     location ^~ /wp-load.php          { deny all; access_log off; log_not_found off; }
     location ^~ /wp-mail.php          { deny all; access_log off; log_not_found off; }
     location ^~ /wp-settings.php      { deny all; access_log off; log_not_found off; }
     location ^~ /wp-signup.php        { deny all; access_log off; log_not_found off; }
     location ^~ /wp-trackback.php     { deny all; access_log off; log_not_found off; }
     location ^~ /xmlrpc.php           { deny all; access_log off; log_not_found off; }

     set $mobile '';
     if ($http_user_agent ~* '(DoCoMo|J-PHONE|Vodafone|MOT-|UP.Browser|DDIPOCKET|ASTEL|PDXGW|Palmscape|Xiino|sharp pda browser|Windows CE|L-mode|WILLCOM|SoftBank|Semulator|Vemulator|J-EMULATOR|emobile|mixi-mobile-converter)') {
       set $mobile '@ktai';
     }
     if ($http_user_agent ~* '(iPhone|iPod|incognito|webmate|Android|dream|CUPCAKE|froyo|BlackBerry|webOS|s8000|bada|IEMobile|Googlebot-Mobile|AdsBot-Google)') {
       set $mobile '@smartphone';
     }
     if ($http_cookie ~* "wptouch(_switch_cookie=normal|-pro-view=desktop)") {
         set $mobile "@smartphone.desktop";
     }

     location ^~ /wp-content/uploads/ {
         expires 30d;
#         rewrite ^ http://static.tbsradio.jp$request_uri? permanent;
     }

     location ~* /wp-(content|admin|includes) {
         index   index.php index.html index.htm;
         if ($request_filename ~* .*.(xml|gz)) {
             break;
             expires 1d;
         }
         if ($request_filename ~* .*.(txt|html|js|css|swf)) {
             break;
             expires 30d;
         }
         if ($request_filename ~* .*.(ico|jpeg|gif|png|wmv|flv|mpg|gz)) {
             break;
             expires 365d;
         }
         if ($request_filename ~ .*.php) {
             break;
             proxy_pass http://backend;
         }
     }

     location ~* (.*).(gif|jpe?g|JPG|png|ico) {
#        rewrite ^ http://static.tbsradio.jp$request_uri? permanent;
     }

     location ~* (.*).(css|less|js) {
         break;
     }

     location /feed {
         proxy_pass http://backend;
     }
     location ~ .*.php {
         proxy_pass http://backend;
     }

#     error_log /var/log/nginx/elb_error.log;
#     empty_gif;
#     break;



     location @wordpress {
#        ssi on;
         set $do_not_cache 0;
         if ($http_cookie ~* "comment_author_|wordpress_( !test_cookie)|wp-postpass_" ) {
             set $do_not_cache 1;
         }
         if ($request_method = POST) {
             set $do_not_cache 1;
         }

         proxy_no_cache     $do_not_cache;
         proxy_cache_bypass $do_not_cache;
         proxy_read_timeout 300;
         proxy_redirect     off;
         proxy_cache        one;
         proxy_cache_key    "$scheme://$host$request_uri$mobile";
         proxy_cache_valid  200 10m;
         proxy_cache_valid  404 1m;
#         proxy_set_header Try-Redirect-To-File $redirect_to;
         proxy_pass         http://backend;
     }

     location / {
         root /var/www/html;
         auth_basic “REstricted”;
         auth_basic_user_file /var/www/html/.htpasswd;

         try_files $uri @wordpress;
     }

     location ~ .xml {
         rewrite ^/sitemap.xml$ /index.php?sitemap=1 last;
         rewrite ^/sitemap_index.xml$ /index.php?sitemap=1 last;
         rewrite ^/([^/]+?)-sitemap([0-9]+)?.xml$ /index.php?sitemap=$1&sitemap_n=$2 last;
         try_files $uri @wordpress;
     }


     location /healthcheck.html {
         satisfy   any;
         allow     all;
     }

     #error_page 404 /404.html;
     #    location = /40x.html {
     #}

        # redirect server error pages to the static page /50x.html
        #
     #error_page 500 502 503 504 /50x.html;
     #    location = /50x.html {
     #}


     #location /s3/ {
     #     proxy_set_header Authorization "";
     #     proxy_pass https://s3-ap-northeast-1.amazonaws.com/hogehoge-static-data/s3/;
     #}

}

ドキュメントルートディレクトリ作成

mkdir /var/www/html/hogehoge
chown nginx:nginx /var/www/html/hogehoge

php-fpm系設定

vi /etc/php-fpm.d/www.conf
www.conf
user = nginx
group = nginx
listen = /var/run/php-fpm.sock;
listen.owner = nginx

listen.group = nginx

listen.mode = 0660

security.limit_extensions = .php .html

php-fpmとnginx起動

/etc/rc.d/init.d/php-fpm start
/etc/rc.d/init.d/php-fpm restart

php-fpm.sockのパーミッション調整

chmod 666 /var/run/php-fpm.sock

動作確認

cd /var/www/html/hogehoge/
vi test.php 
test.php
<?php phpinfo();?>

ブラウザでアクセス

http://xxxxxxxxxxxxxxxx.amazonaws.com/hogehoge/test.php

basic認証準備

yum install -y httpd-tools
cd /var/www/html
htpasswd -c .htpasswd loginid
password

hogehoge.confのlocation / に下記追加

hogehoge.conf
location / {
      auth_basic “REstricted”;
      auth_basic_user_file /var/www/html/.htpasswd;  
}

mysqlインストール

yum install -y mysql-server
chkconfig mysqld on
service mysqld start

続きを読む

Serverless Frameworkをつかってバーコード画像APIを作ってみた

結構前にAPI Gatewayがバイナリデータをサポートしたってことで
Serverless Frameworkの勉強がてらになんとなくバーコードAPIを作ってみました。

Serverless Frameworkのインストール

Serverless Frameworkならすぐ出来るよね AWSでサーバーレス(API+Lambda[java]を簡単セットアップ)
とか参考にセットアップします。
※因みに私はWINDOWS環境です:grinning:

E:\workspaces\qiita>npm ls -g serverless
C:\Users\hasegawa\AppData\Roaming\npm
`-- serverless@1.13.2

Serverless Frameworkでつくる

サービスをつくる

Quick Start

とか見ながら

E:\workspaces\qiita>sls create --template aws-nodejs --path barcode
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "E:\workspaces\qiita\barcode"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.13.2
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"

E:\workspaces\qiita>

ライブラリをインストール

とりあえず npm init

E:\workspaces\qiita>cd barcode

E:\workspaces\qiita\barcode>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (barcode)
version: (1.0.0)
description:
entry point: (handler.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to E:\workspaces\qiita\barcode\package.json:

{
  "name": "barcode",
  "version": "1.0.0",
  "description": "",
  "main": "handler.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) yes

E:\workspaces\qiita\barcode>

でバーコードのライブラリをインストール。
bwip-jsを使います。

E:\workspaces\qiita\barcode>npm i bwip-js -S
barcode@1.0.0 E:\workspaces\qiita\barcode
`-- bwip-js@1.3.2
  `-- argv@0.0.2

次にバイナリレスポンスの設定をしてくれるライブラリをインストール。
serverless-apigwy-binary

E:\workspaces\qiita\barcode>npm i -S serverless-apigwy-binary
barcode@1.0.0 E:\workspaces\qiita\barcode
`-- serverless-apigwy-binary@0.1.0
  `-- bluebird@3.5.0

ローカルでAPI Gatewayの動作確認したいので serverless-offlineもインストールします。

E:\workspaces\qiita\barcode>npm i serverless-offline -D
barcode@1.0.0 E:\workspaces\qiita\barcode
`-- serverless-offline@3.14.0
  +-- babel-register@6.24.1
  | +-- babel-core@6.24.1
  | | +-- babel-code-frame@6.22.0
  | | | +-- chalk@1.1.3
  | | | | +-- ansi-styles@2.2.1
  .
  .
  .

でserverless.ymlに plugins... を追加

serverless.yml
service: barcode
plugins: 
 - serverless-apigwy-binary
 - serverless-offline

コードを書く

サンプルをコピペして

handler.js
'use strict';
var bwipjs = require('bwip-js');
bwipjs.loadFont('Inconsolata', 108,
            require('fs').readFileSync('node_modules/bwip-js/fonts/Inconsolata.otf', 'binary'));

module.exports.hello = (event, context, callback) => {
    var param = event.query;
    bwipjs.toBuffer({
            bcid:        param.bcid        || 'code128',       // Barcode type
            text:        param.text        || '0123456789',    // Text to encode
            scale:       param.scale       || 3,               // 3x scaling factor
            height:      param.height      || 10,              // Bar height, in millimeters
            includetext: param.includetext || true,            // Show human-readable text
            textxalign:  param.textxalign  || 'center',        // Always good to set this
            textfont:    param.textfont    || 'Inconsolata',   // Use your custom font
            textsize:    param.textsize    || 13               // Font size, in points
        }, function (err, png) {
            if (err) {
                callback(null, "[BadRequest] " + err);
            } else {
                // `png` is a Buffer
                callback(null, png.toString('base64'));
            }
        });
};

バイナリの返却はbase64エンコードします。

次にserverless.ymlの events の箇所のコメントアウトをはずして下記のようにします

serverless.yml
    events:
      - http:
          path: barcode
          method: get
          integration: lambda
          contentHandling: CONVERT_TO_BINARY
          request:
            parameters:
              querystrings:
                bcid: false
                text: false
                scale: false
                height: false
                includetext: false
                textxalign: false
                textfont: false
                textsize: false

integrationのデフォルトは lambda-proxy なので lambdaを追記します。

※lambda-proxyの方が簡単なんですがどうしてもうまくいきませんでした。。
バイナリがサポートされてない感じです
https://forums.aws.amazon.com/thread.jspa?threadID=243584&tstart=0

この辺でローカルで確認する

Serverless Offlineを動かして

E:\workspaces\qiita\barcode>sls offline start
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for hello:
Serverless: GET /barcode

Serverless: Offline listening on http://localhost:3000

ブラウザでhttp://localhost:3000/barcode
にリクエストを出してこんな感じになればOKです。

Serverless: [200] "iVBORw0KGgoAAAANSUhEUgAAAQ4AAABzCAMAAABepBw8AAAAHnRFWHRTb2Z0d2FyZQBid2lwLWpzLm1l
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hic7ZlrQFVVFsfP/4omvsAHyEWBKc2rUhlqkg5oKr7CULuKSmhKimnpzAiaZiOiYoNmiqaGTmSlaGomaIT5SinSxrERcqxselA6
/V+yCbnNQOsmi+3pbwDi7c9JYhZB3fI4ql+PmfLfwYWGZlWokOoMStNvBgnK3UrkjThVbEiXjUqtjmlCJuOiz2Nnidr7S89fkNT

因みに2回目以降のリクエストはエラーになります・・。
Serverless Offlineをリスタートするしかないです。
(理由は調べてません。。:sweat_smile:

デプロイ

E:\workspaces\qiita\barcode>sls deploy
Serverless: Packaging service...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (5.48 MB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............................
Serverless: Stack update finished...
Service Information
service: barcode
stage: dev
region: us-east-1
api keys:
  None
endpoints:
  GET - https://xxxxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/barcode
functions:
  hello: barcode-dev-hello
Serverless: Setting up content handling in AWS API Gateway (takes ~1 min)...
Serverless: Deploying content handling updates to AWS API Gateway...
Serverless: AWS API Gateway Deployed

でendpointsをブラウザでたたくと

sample.png

・・・あれ?Content-Typeがイケてないようです。。

Accept: image/png
つけるとOKですね。

sample4.png

この辺りは調べて追記することにします。。

続きを読む

AWSのサービスでドメインを取得しALBでSSLで接続出来るようにする

概要

表題の通りですが、AWSのサービスのみで独自ドメインの取得を行いALBで公開を行います。
なお公開するWebサービスはSSLで接続可能にしますので、AWS Certificate Manager で証明書の取得も行います。

前提条件

ALB(Application Load Balancer) が作成されている必要があります。

※以前、ALB(Application Load Balancer)でWebサービスを冗長化する という記事を書きました。参考になれば幸いです。

ドメインの登録を行う

Route53のドメイン登録画面に遷移する

マネジメントコンソールからRoute53サービスの “Domain registration” に進みます。

domain-step1.png

“Register Domain”を選択します。

domain-step2.png

取得しようと思っているドメイン名が利用可能かどうかをチェックする

希望のドメイン名を入力し、”Check”を選択します。
トップドメインによって必要な登録料が変わってきます。
今回は検証用なので比較的易い.netを選択しました。

domain-step3.png

“Add to cart” を押して次の手続きに進みます。

必要事項を入力

必要事項を入力します。

domain-step4.png

デフォルトでチェックが入っていますが、”Hide contact information if the TLD registry, and the registrar, allow it” にチェックが入っているか確認を行います。

※ここにチェックが入っていると、whoisでドメインの管理情報が照会された時に、登録者であるAmazonの情報が表示されプライバシーを保護できます。

domain-step5.png

確認画面

“I have read and agree to the AWS Domain Name Registration Agreement” にチェックを入れて “Complete Purchase” をクリックします。

以上でドメインの申請は完了となります。

domain-step7.png

登録完了後しばらくすると、ドメイン登録時に入力したメールアドレスに「Verify your email address or your domain smsc-nsc.net will be suspended」 というタイトルのメールがAWSから届きます。

リンク内のURLをクリックしメールアドレスの検証を完了させましょう。

サブドメインの設定を行う

私が公開作成しようと思っているサービスは複数のサブドメインから成り立つ構成なので、サブドメインの登録を行い既に起動しているALBに対し任意のサブドメインを割り当てます。

Route53の「Hosted zones」を選択し先程登録したドメインを選択します。

subdomain-step1.png

「Create Record Set」を選択し新規レコードを作成します。

subdomain-step2.png

入力情報
Name: 登録したいサブドメイン名
Type: A - IPv4 addressを選択
Alias: Yesを選択
Alias Target: ALB(Application Load Balancer) を選択

subdomain-step3.png

「Create」を選択してから、しばらく待つと登録したサブドメインでALB(Application Load Balancer)にアクセス出来る事が確認出来るかと思います。

Certificate Managerで証明書を作成する

無料のSSL証明書を取得します。
証明書の更新が自動で料金も無料と魅力的ですが、いくつか制限があるので注意が必要です。
今回、私が利用する条件的には十分だったので問題はありませんでしたが、これらの条件のうちどれかが必須要件だった場合は別の手段で証明書を取得する必要があります。

  • AWS上の一部のサービスでしか使えない

  • EV SSL証明書としては利用出来ない

証明書の作成リクエストを送る

マネジメントコンソールから東京リージョンのCertificate Managerサービスにアクセスします。

CertificateManager-step1.png

「今すぐ始める」から証明書のリクエストを開始します。
今回は複数のサブドメインで運用を行うのでワイルドカード証明書を取得します。

*.hoge.net のように取得したドメインを入力します。

CertificateManager-step2.png

CertificateManager-step3.png

「確定とリクエスト」をクリックすると、ドメイン登録時に登録したメールアドレス宛に 「Certificate approval for Requestしたドメイン名」というタイトルのメールが送信されてきますので本文内のリンクから手続きに進みます。

CertificateManager-step5.png

“I Approve” をクリックすると手続きは完了となります。

CertificateManager-step5.png

CertificateManager-step6.png

ALB(Application Load Balancer)に作成した証明書を適応する

マネジメントコンソールからEC2サービス → ロードバランサーから証明書を適応するロードバランサーを選択します。

alb-step1.png

「リスナーの追加」から以下の内容を入力します。

プロトコル: HTTPS(セキュア HTTP)
デフォルトターゲットグループ: 作成済のターゲットグループ
AWS 証明書マネージャ (ACM) から、既存の証明書を選択する

alb-step2.png

しばらくすると対象のロードバランサーにHTTPSでアクセス出来る事が確認出来るハズです。

ちなみに私の場合はHTTPSアクセスが確認出来次第、HTTPのリスナーは削除するようにしました。

最後に

AWSのサービスを利用すると非常に簡単にSSLでWebサイトの公開が可能です。

EV SSLが必須の場合この方法は使えませんが、ちょっとしたサービスを作るのであれば良い選択肢だと思います。

最後まで読んで頂きありがとうございました。

続きを読む

AWS ECSにてカスタムしたredmineのdockerイメージを動かすまでのメモ(その1)

redmineの構築、プラグインの導入をふつーにやると面倒くさい。
あと、一旦構築した後redmineのバージョンをあげるのもやっぱり面倒くさい。

→ので、dockerにてプラグインのインストールやらなにやらを手順をコード化して簡単にRedmineの導入が
できるようにしました。
なお動作環境はAWSのECS(Elastic Container Service)を使います。

大きな流れ

1.Dockerfileを用意
2.AWSにてElastic Container Service(ECS)のタスクを定義
3.ECSのインスタンスを用意

今回はまず1を用意します。

1.Dockerfileを用意

redmineの公式イメージがdockerhubにあるのでこれをもとにpluginを導入する手順を
dockerfile化していきます。

ポイントは2つです。
1.ベースのイメージはredmine:x.x.x-passengerを利用する
2.DBのマイグレーションが必要ないものはpluginsフォルダに配置し、マイグレーションが必要なものは別フォルダ(install_plugins)に配置。
→コンテナ起動時にマイグレーションを行うようdocker-entrypoint.shに記載しておきます。

インストールするプラグイン一覧

独断と偏見で入れたプラグインです。

No プラグインの名前 概要
1 gitmike githubの雰囲気のデザインテーマ
2 backlogs スクラム開発でおなじみ。ストーリーボード
3 redmine_github_hook redmineとgitを連動
4 redmine Information Plugin redmineの情報を表示可能
5 redmine Good Job plugin チケット完了したら「Good Job」が表示
6 redmine_local_avatars アイコンのアバター
7 redmine_startpage plugin 初期ページをカスタマイズ
8 clipboard_image_paste クリップボードから画像を添付できる 
9 Google Analytics Plugin 閲覧PV測定用
10 redmine_absolute_dates Plugin 日付を「XX日前」 ではなくyyyy/mm/ddで表示してくれる
11 sidebar_hide Plugin サイドバーを隠せる
12 redmine_pivot_table ピボットテーブルできる画面追加
13 redmine-slack 指定したslack channnelに通知可能
14 redmine Issue Templates チケットのテンプレート
15 redmine Default Custom Query 一覧表示時のデフォルト絞り込みが可能に
16 redmine Lightbox Plugin 2 添付画像をプレビューできる
17 redmine_banner Plugin 画面上にお知らせを出せます
18 redmine_dmsf Plugin フォルダで文書管理できる
19 redmine_omniauth_google Plugin googleアカウントで認証可能
20 redmine view customize Plugin 画面カスタマイズがコードで可能

Dockerfile

上記のプラグインをインストール済みとするためのDockerfileです。
なお、ベースイメージは最新(2017/05/19時点)の3.3.3を使用しています。

Dockerfile
FROM redmine:3.3.3-passenger
MAINTAINER xxxxxxxxxxxxxxxxx

#必要コマンドのインストール
RUN apt-get update -y 
 && apt-get install -y curl unzip ruby ruby-dev cpp gcc libxml2 libxml2-dev 
  libxslt1-dev g++ git make xz-utils xapian-omega libxapian-dev xpdf 
  xpdf-utils antiword catdoc libwpd-tools libwps-tools gzip unrtf 
  catdvi djview djview3 uuid uuid-dev 
 && apt-get clean

#timezoneの変更(日本時間)
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
ENV RAILS_ENV production

#gitmakeテーマのインストール
RUN cd /usr/src/redmine/public/themes 
 && git clone https://github.com/makotokw/redmine-theme-gitmike.git gitmike 
 && chown -R redmine.redmine /usr/src/redmine/public/themes/gitmike



#redmine_github_hookのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/koppen/redmine_github_hook.git

#Redmine Information Pluginのインストール
RUN curl http://iij.dl.osdn.jp/rp-information/57155/rp-information-1.0.2.zip > /usr/src/redmine/plugins/rp-information-1.0.2.zip 
 && unzip /usr/src/redmine/plugins/rp-information-1.0.2.zip -d /usr/src/redmine/plugins/ 
 && rm -f /usr/src/redmine/plugins/rp-information-1.0.2.zip

#Redmine Good Job pluginのインストール
RUN curl -L https://bitbucket.org/changeworld/redmine_good_job/downloads/redmine_good_job-0.0.1.1.zip > /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip 
 && unzip /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip -d /usr/src/redmine/plugins/redmine_good_job 
 && rm -rf /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip

#redmine_startpage pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/txinto/redmine_startpage.git

#Redmine Lightbox Plugin 2 Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/peclik/clipboard_image_paste.git

#Google Analytics Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/paginagmbh/redmine-google-analytics-plugin.git google_analytics_plugin

#redmine_absolute_dates Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/suer/redmine_absolute_dates

#sidebar_hide Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/bdemirkir/sidebar_hide.git

#redmine_pivot_tableのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/deecay/redmine_pivot_table.git

#redmine-slackのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/sciyoshi/redmine-slack.git redmine_slack

#Redmine Issue Templates Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_issue_templates.git redmine_issue_templates

#Redmine Default Custom Query Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/hidakatsuya/redmine_default_custom_query.git redmine_default_custom_query

#Redmine Lightbox Plugin 2 Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/paginagmbh/redmine_lightbox2.git redmine_lightbox2

#redmine_banner Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_banner.git redmine_banner

#redmine_dmsf Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/danmunn/redmine_dmsf.git redmine_dmsf

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/yamamanx/redmine_omniauth_google.git redmine_omniauth_google

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/onozaty/redmine-view-customize.git view_customize

#redmine_local_avatars用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/ncoders/redmine_local_avatars.git

#backlogsのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN mkdir /usr/src/redmine/install_plugins 
 && cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/AlexDAlexeev/redmine_backlogs.git 
 && cd /usr/src/redmine/install_plugins/redmine_backlogs/ 
 && sed -i -e '11,17d' Gemfile

#database.ymlファイルを置くフォルダを用意
RUN mkdir /config
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh

ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 3000
CMD ["passenger", "start"]

docker-entrypoint.sh

次にdocker-entrypoint.shファイルです。
githubに公開されているファイル
(https://github.com/docker-library/redmine/blob/41c44367d9c1996a587e2bcc9462e4794f533c15/3.3/docker-entrypoint.sh)
を元にプラグインのインストールを行うコードを記載していきます。

docker-entrypoint.sh
#!/bin/bash
set -e

case "$1" in
    rails|rake|passenger)
        if [ -e '/config/database.yml' ]; then
                    if [ ! -f './config/database.yml' ]; then
                echo "use external database.uml file"
                ln -s /config/database.yml /usr/src/redmine/config/database.yml
            fi
        fi
                if [ -e '/config/configuration.yml' ]; then
                        if [ ! -f './config/configuration.yml' ]; then
                                echo "use external configuration.uml file"
                                ln -s /config/configuration.yml /usr/src/redmine/config/configuration.yml
                        fi
                fi
        if [ ! -f './config/database.yml' ]; then
            if [ "$MYSQL_PORT_3306_TCP" ]; then
                adapter='mysql2'
                host='mysql'
                port="${MYSQL_PORT_3306_TCP_PORT:-3306}"
                username="${MYSQL_ENV_MYSQL_USER:-root}"
                password="${MYSQL_ENV_MYSQL_PASSWORD:-$MYSQL_ENV_MYSQL_ROOT_PASSWORD}"
                database="${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}"
                encoding=
            elif [ "$POSTGRES_PORT_5432_TCP" ]; then
                adapter='postgresql'
                host='postgres'
                port="${POSTGRES_PORT_5432_TCP_PORT:-5432}"
                username="${POSTGRES_ENV_POSTGRES_USER:-postgres}"
                password="${POSTGRES_ENV_POSTGRES_PASSWORD}"
                database="${POSTGRES_ENV_POSTGRES_DB:-$username}"
                encoding=utf8
            else
                echo >&2 'warning: missing MYSQL_PORT_3306_TCP or POSTGRES_PORT_5432_TCP environment variables'
                echo >&2 '  Did you forget to --link some_mysql_container:mysql or some-postgres:postgres?'
                echo >&2
                echo >&2 '*** Using sqlite3 as fallback. ***'

                adapter='sqlite3'
                host='localhost'
                username='redmine'
                database='sqlite/redmine.db'
                encoding=utf8

                mkdir -p "$(dirname "$database")"
                chown -R redmine:redmine "$(dirname "$database")"
            fi

            cat > './config/database.yml' <<-YML
                $RAILS_ENV:
                  adapter: $adapter
                  database: $database
                  host: $host
                  username: $username
                  password: "$password"
                  encoding: $encoding
                  port: $port
            YML
        fi

        # ensure the right database adapter is active in the Gemfile.lock
        bundle install --without development test
        if [ ! -s config/secrets.yml ]; then
            if [ "$REDMINE_SECRET_KEY_BASE" ]; then
                cat > 'config/secrets.yml' <<-YML
                    $RAILS_ENV:
                      secret_key_base: "$REDMINE_SECRET_KEY_BASE"
                YML
            elif [ ! -f /usr/src/redmine/config/initializers/secret_token.rb ]; then
                rake generate_secret_token
            fi
        fi
        if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then
            gosu redmine rake db:migrate
        fi

        chown -R redmine:redmine files log public/plugin_assets

        if [ "$1" = 'passenger' ]; then
            # Don't fear the reaper.
            set -- tini -- "$@"
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_backlogs ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_backlogs /usr/src/redmine/plugins/
                        bundle update nokogiri
                        bundle install
                        bundle exec rake db:migrate
                        bundle exec rake tmp:cache:clear
                        bundle exec rake tmp:sessions:clear
            set +e
                        bundle exec rake redmine:backlogs:install RAILS_ENV="production"
                        if [ $? -eq 0 ]; then
                echo "installed backlogs"
                                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
                        fi
            set -e
            touch /usr/src/redmine/plugins/redmine_backlogs/installed
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_local_avatars ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_local_avatars /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
                fi
        if [ -e /usr/src/redmine/install_plugins/redmine_slack ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_slack /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_issue_templates ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_issue_templates /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_default_custom_query ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_default_custom_query /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_lightbox2 ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_lightbox2 /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_banner ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_banner /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_dmsf ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_dmsf /usr/src/redmine/plugins/
            bundle install --without development test xapian
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_omniauth_google ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_omniauth_google /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/view_customize ]; then
            mv -f /usr/src/redmine/install_plugins/view_customize /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ ! -f '/usr/src/redmine/plugins/redmine_backlogs/installed' ]; then
            set +e
            bundle exec rake redmine:backlogs:install RAILS_ENV="production"
            if [ $? -eq 0 ]; then
                echo "installed backlogs"
                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
            fi
            set -e
        fi
        set -- gosu redmine "$@"
                ;;
esac

exec "$@"

次はこのイメージをAWSのECSにて動作させます。
(次回に続く)

続きを読む

AWS Lambdaデプロイ方法を求めて:Serverlessフレームワーク

このドキュメントレベル:初めて学ぶ人向け

Lambdaの良いデプロイフローはないかと思って調べた記録です。
Lambdaのデプロイにはいくつか種類があるようです。

  • Serveless
  • Apex
  • Lamvery
  • LambCI
  • CodeShip

今回は「Serverless」について、どんなものかを触りだけ調べています。

Serverless

サーバーレスなアーキテクチャを容易に作成、管理できるフレームワークです
AWS Lambda, Apache OpenWhisk, Microsoft Azureなどをサポートしているようです。

デプロイイメージをつかむ

  • このnode.jsのサンプルを実際にやってみると感じがよくわかります。事前のAWSのCredentialsの設定をやっておきましょう

Hello World Node.js Example

Serverless Framework – AWS Lambda Guide – Credentials

豊富なサンプルコード

gutHubにサンプルコードがアップされているので、これを実行するだけでも感じがつかめます

serverless/examples: Serverless Examples – A collection of boilerplates and examples of serverless architectures built with the Serverless Framework and AWS Lambda

例)aws-node-rest-api-with-dynamodb/

cloneしてきて、deployしてみます

14:45:35 aws-node-rest-api-with-dynamodb/  $ ls
README.md   package.json    serverless.yml  todos


14:46:01 aws-node-rest-api-with-dynamodb/  $ npm install
aws-rest-with-dynamodb@1.0.0 /Users/bohebohechan/devel/src/gitlab.com/FirstFourNotes/serverless/aws-node-rest-api-with-dynamodb
└── uuid@2.0.3

npm WARN aws-rest-with-dynamodb@1.0.0 No repository field.

11:23:44 aws-node-rest-api-with-dynamodb/  $ sls deploy
Serverless: Packaging service...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (22.45 KB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
......................................................................................................
Serverless: Stack update finished...
Service Information
service: serverless-rest-api-with-dynamodb
stage: dev
region: us-east-1
api keys:
  None
endpoints:
  POST - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/todos
  GET - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/todos
  GET - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/todos/{id}
  PUT - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/todos/{id}
  DELETE - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/todos/{id}
functions:
  create: serverless-rest-api-with-dynamodb-dev-create
  list: serverless-rest-api-with-dynamodb-dev-list
  get: serverless-rest-api-with-dynamodb-dev-get
  update: serverless-rest-api-with-dynamodb-dev-update
  delete: serverless-rest-api-with-dynamodb-dev-delete
11:25:19 aws-node-rest-api-with-dynamodb/  $

RestAPIができてしまったようです。

AWSコンソールで確認

実際にコンソールで確認してみましょう

> Lambda

lambda.png

> DynamoDB

キャプチャ撮り忘れたので割愛・・・

> API Gateway

apigateway.png

> S3

s3.png

> CloudFormation

cloudformation.png

> CloudWatch Logs

cloudwatchlogs.png

PostmanでAPIを実行してみます

> Post

postman-post.png

> Get

postman-list.png

後片付け

消しておきましょう

11:44:47 aws-node-rest-api-with-dynamodb/  $ sls remove
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
..............................................................
Serverless: Stack removal finished...

このようなサンプルをカスタマイズしていくことで、自分の欲しい機能を簡単に作っていけそうです。

serverless.yml

先ほどの例)aws-node-rest-api-with-dynamodb/を参考にして、serverless.ymlの中身を紐解いてみましょう
解説はざっくりなので、詳細は公式ページで確認のことです。

AWSをプロバイダーにセットしたときに有効となるプロパティ一覧が載っています
Serverless Framework – AWS Lambda Guide – Serverless.yml Reference

  • service

プロジェクト名

  • frameworkVersion

フレームワークの対応バージョン

service: serverless-rest-api-with-dynamodb

frameworkVersion: ">=1.1.0 <2.0.0"
  • provider

AWS CloudFormation stack
サービスがデプロイされる対象について書きます。ここでは、AWSですよね 
 
– iamRoleStatements
How it works iamRoleStatements configuration section? – Serverless Framework – Serverless Forums

Lambda Functionで、AWSのリソースにアクションする際の許可をAWS IAM Roleで記述します。
「provider.iamRoleStatements」のプロパティに必要となる許可ステートメントを設定します。
今回は、Lambdaからdynamodbへの許可が必要ですね。

provider:
  name: aws
  runtime: nodejs6.10
  environment:
    DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

-Functions

Lambdaに作成するFunctionの定義を書きます

  • Events

Lambda Function の起動するトリガーを書きます
S3バケットへのアップロードや、SNSトピック受信や、HTTPのエンドポイントですね

サポートしているイベントの一覧はこちら
Serverless – AWS Lambda – Events

functions:
  create:
    handler: todos/create.create
    events:
      - http:
          path: todos
          method: post
          cors: true

  list:
    handler: todos/list.list
    events:
      - http:
          path: todos
          method: get
          cors: true

  get:
    handler: todos/get.get
    events:
      - http:
          path: todos/{id}
          method: get
          cors: true

  update:
    handler: todos/update.update
    events:
      - http:
          path: todos/{id}
          method: put
          cors: true

  delete:
    handler: todos/delete.delete
    events:
      - http:
          path: todos/{id}
          method: delete
          cors: true
  • Resources

AWS CloudFormation stackに追加することができます。
以下では、TodosDynamoDbTableを追加しています。
特定のCloudFormationのリソースに対して、値を上書きすることもできます

resources:
  Resources:
    TodosDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: id
            AttributeType: S
        KeySchema:
          -
            AttributeName: id

ワークフロー

Serverless Framework Guide – AWS Lambda – Workflow

Development Workflowとして以下の手順で回しましょう、といったことが書かれています。

  1. Write your functions
  2. Use serverless deploy only when you’ve made changes to serverless.yml and in CI/CD systems.
  3. Use serverless deploy function -f myFunction to rapidly deploy changes when you are working on a specific AWS Lambda Function.
  4. Use serverless invoke -f myFunction -l to test your AWS Lambda Functions on AWS.
  5. Open up a separate tab in your console and stream logs in there via serverless logs -f myFunction -t.
  6. Write tests to run locally.

参考になるドキュメント

続きを読む

Setup Rails on unicorn on nginx on AWS EC2 linux

How to install Unicorn

1.Modify Gemfile contents

$ sudo vim ~/(sampleapp)/Gemfile

Add gem 'unicorn'

2. Install unicorn at the Gemfile’s directory

$ bundle install

3. Check installation of unicorn

$ bundle show unicorn
/home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/unicorn-5.3.0

4. Make “unicorn.rb” on /(sampleAppDirectory)/config and write following contents

unicorn.rb
  worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
  timeout 15
  preload_app true

  listen '/home/ec2-user/testApp/tmp/unicorn.sock' #{Railsアプリケーションのあるディレクトリ}/tmp/unicorn.sock
  pid    '/home/ec2-user/testApp/tmp/unicorn.pid' #{Railsアプリケーションのあるディレクトリ}/tmp/unicorn.pid

  before_fork do |server, worker|
    Signal.trap 'TERM' do
      puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
      Process.kill 'QUIT', Process.pid
    end

    defined?(ActiveRecord::Base) and
      ActiveRecord::Base.connection.disconnect!
  end

  after_fork do |server, worker|
    Signal.trap 'TERM' do
      puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
    end

    defined?(ActiveRecord::Base) and
      ActiveRecord::Base.establish_connection
  end

  stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
  stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

5. Execute following code and create rake file at your app directory

$ rails g task unicorn 
Running via Spring preloader in process 13140
      create  lib/tasks/unicorn.rake

6. Start unicorn and check unicorn starting

$ bundle exec unicorn_rails -c config/unicorn.rb 

*↑production環境かdeployment環境は選べる。Unicorn commands一覧参照

$ ps -ef | grep unicorn
ec2-user 12631 11576  0 08:09 pts/1    00:00:00 vim unicorn.rb
ec2-user 13547 13151  0 08:37 pts/5    00:00:01 unicorn master -c config/unicorn.rb                                                                                            
ec2-user 13582 13547  0 08:37 pts/5    00:00:00 unicorn worker[0] -c config/unicorn.rb                                                                                         
ec2-user 13584 13547  0 08:37 pts/5    00:00:00 unicorn worker[1] -c config/unicorn.rb                                                                                         
ec2-user 13587 13547  0 08:37 pts/5    00:00:00 unicorn worker[2] -c config/unicorn.rb                                                                                         
ec2-user 14665 13960  0 08:51 pts/3    00:00:00 grep --color=auto unicorn

Modify the setting of Nginx

1. Modify nginx.conf to rails.cof

nginx/1.10.2時点ではnginx.conf($ nginx -tでconfigファイルを確認できる)。nginx.confに以下のように書き換える。

nginx.conf
# For mor# e information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user ec2-user;
worker_processes 1;

events {
    worker_connections 1024;
}

http {
#    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
#                     '$status $body_bytes_sent "$http_referer" '
#                      '"$http_user_agent" "$http_x_forwarded_for"';

#   access_log  /home/ec2-user/testApp/access.log  main;

#    sendfile            on;
#    tcp_nopush          on;
#    tcp_nodelay         on;
#    keepalive_timeout   65;
#    types_hash_max_size 2048;

#    include             /etc/nginx/mime.types;
#    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
#   include /etc/nginx/conf.d/*.conf;

#   index   index.html index.htm;

    upstream unicorn {
      server  unix:/home/ec2-user/testApp/tmp/unicorn.sock; #/home/{ユーザ名}/{Railsアプリケーション>名}/tmp/unicorn.sock
    }

    server {
        listen       xx; #HTTP = 80
        server_name  xxx.xxx.xxx.xxx; #Your server ip-address or domain name

        access_log  /var/log/access.log;
        error_log   /var/log/error.log;

        root    /home/ec2-user/testApp/public; #/home/{ユーザ名}/{Railsアプリケーション名}/public
#       index   index.html;
        client_max_body_size 4G; #100m;
        error_page  404              /404.html;
        error_page  500 502 503 504  /500.html;
        try_files   $uri/index.html $uri @unicorn;

        location @unicorn {
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_pass http://unicorn;
                }

        }

2. Reload nginx

$ sudo service nginx restart

3. Access your server address!!!

ここからはunicornのnginxに対するpermission deniedが出た時の処理

2017/04/23 06:45:40 [crit] 29912#0: *1 open()
"/var/lib/nginx/tmp/proxy/1/00/0000000001" failed (13: Permission
denied) while reading upstream, client: (clientserver), server:
(my-server), request: "GET / HTTP/1.1", upstream:
"http://unix:/pathTo/tmp/unicorn.sock:/", host:
"(my-server)"

上記エラーではnginx, tmpにおけるアクセス権限がnginxのみしかないので、アクセス権を解放する(下記アクセスは777としているが修正する必要あり)

$ sudo chmod 777 nginx
$ sudo chmod 777 tmp

続きを読む

AIDEを使ってファイルの改竄検知を行う。

はじめに

ファイル改竄検知に AIDE(Advanced Intrusion Detection Environment) というのがあります。
オープンソースでAmazon Linuxにもリポジトリが存在するのでyumで簡単に導入することが可能です。

インストール

リポジトリが存在するのでyumコマンドで簡単にインストールできます。

インストール
$ sudo yum install aide

データベースの初期化

インストールできたらまずは初期化を行います。

初期化
$ sudo aide -i

AIDE, version 0.14

### AIDE database at /var/lib/aide/aide.db.new.gz initialized.

初期化が完了すると aide.db.new.gz というファイルが作成されますが、デフォルトの参照ファイルは aide.db.gz であるため、リネームします。

データベース配置
$ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

改竄チェック

それではチェックしてみましょう。

改竄チェック
$ sudo aide --check

AIDE, version 0.14

### All files match AIDE database. Looks okay!

okayとあるため特に問題はないみたいですがそりゃそうですよね。何もしてないですからw
ということで、 /root 配下に適当にファイルを作成してみます。

ファイル配置
$ sudo touch /root/aide-test.txt

それではもう一度チェックしてみましょう。

改竄チェック
$ sudo aide --check
AIDE found differences between database and filesystem!!
Start timestamp: 2017-04-28 17:36:55

Summary:
  Total number of files:    60760
  Added files:          1
  Removed files:        0
  Changed files:        1


---------------------------------------------------
Added files:
---------------------------------------------------

added: /root/aide-test.txt

---------------------------------------------------
Changed files:
---------------------------------------------------

changed: /root

--------------------------------------------------
Detailed information about changes:
---------------------------------------------------


Directory: /root
  Mtime    : 2017-04-28 17:28:16              , 2017-04-28 17:36:47
  Ctime    : 2017-04-28 17:28:16              , 2017-04-28 17:36:47

先ほど作成したファイルが表示されました。追加/削除/変更が確認できるので便利ですね。

更新

このままだとチェックするたびに変更が増えていってしまいますね。
なのでデータベースを更新する必要があります。更新は初期化してあげればOKです。

データベース更新
$ sudo aide --init

AIDE, version 0.14

### AIDE database at /var/lib/aide/aide.db.new.gz initialized.
データベース更新
$ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

これだと毎回「チェック → 更新」を繰り返さないといけないので面倒です。
そんな場合はupdateオプションを利用すれば解決です。

このオプションはチェックを行なった後、データベースの更新をしてくれます。

オプション

主要なオプションについてまとめました。

オプション 説明
–init または -i データベースの初期化
–check または -C データベースのチェック
–update または -u データベースのチェックと更新

おわりに

これをcronとかで定期実行すればファイル改竄検知が手軽にできてとても便利ですね。

続きを読む