〇FLAGの中の人に憧れてMastodon×AWSでモンストドン作ってみた

Mastodon立ち上げたらいい会社に入れると聞いて、邪な気持ちで。。。いや、Mastodonとモンストって相性よさそうだなぁと思いたち、少し乗り遅れた感をかもしだしながら、フルにAWSを使って規模拡大しても大丈夫な構成で作ってみた。

モンストドン (https://monstdn.com)

構成

monstdn.png

最小構成のざっくり料金($1=113円、1ヶ月30日計算)

サービス 単価 月額料金
ALB 1台 × $0.0243/1H + データ転送的なの  約2000円 + α
EC2 2台(t2.nano) × $0.008/1H + データ転送的なの 約1300円 + α
RDS 1台(db.t2.micro シングルAZ) $0.028/1H + データ転送的なの 約2300円 + α
ElasticCache 1台(cache.t2.micro) $0.026/1H + データ転送的なの 約2100円 + α
S3Bucket $0.025/GB + リクエスト数的なの + α
SES $0.10/1,000通あたり + データ転送的なの + α
合計     ( 約7700円 + α なので ) ざっくり1万ぐらい

※無料枠があるので1年目はもう少しやすくできそう

やったこと

  • AWSのアカウント作成
  • IAMの作成とアカウントの初期設定(二段階認証とか、パスワードポリシーとか)
  • Route53でドメインを買う
  • SESでメール設定と制限解除申請
  • ACMの取得(無料でHTTPS通信)
  • S3バケット作成(画像とかのアップロードファイルの配信用)
  • VPCとセキュリティグループの作成
  • SES、S3へアクセスする為のIAMユーザの作成
  • ElasticCacheでRedisの作成
  • RDSでPostgreSQLの作成
  • EC2でCentOSを使ってMastodonの構築(下に詳細)とイメージ(AMI)の作成
  • AutoScallingの設定
  • ALB(ApplicationLoadBalancer)の作成(ACMをつける)
  • Route53でHostZoneのレコード設定

CentOSでのMastdon構築(20170517現在)

sudo su -
yum -y update
yum -y install vim

localectl set-locale LANG=ja_JP.utf8
localectl set-keymap jp106
localectl status

timedatectl set-timezone Asia/Tokyo
timedatectl status

dd if=/dev/zero of=/mnt/swapfile bs=1M count=2560
mkswap /mnt/swapfile
swapon /mnt/swapfile
chmod 0644 /mnt/swapfile
echo "/mnt/swapfile                             swap                    swap    defaults                0 0" >> /etc/fstab
free

vim /etc/sysconfig/selinux
 SELINUX=enforcing
 ↓
 SELINUX=disabled

systemctl disable postfix
systemctl disable auditd.service

yum -y install libxml2-devel ImageMagick libxslt-devel git curl nodejs file
yum -y install epel-release
rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
yum -y install ffmpeg ffmpeg-devel

yum -y group install "Development tools"
curl -sL https://rpm.nodesource.com/setup_4.x | sudo bash -

yum -y install nodejs
npm -g install yarn

yum -y install postgresql postgresql-contrib postgresql-devel
yum install -y openssl-devel readline-devel

useradd mastodon
passwd mastodon
su - mastodon
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src && cd ~
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile && source ~/.bash_profile
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 2.4.1 && rbenv global $_ && rbenv rehash

# 確認
ruby -v

cd ~
git clone https://github.com/tootsuite/mastodon.git live
cd live
git checkout $(git tag | tail -n 1)

gem install bundler
bundle install --deployment --without development test
yarn install --pure-lockfile

cp .env.production.sample .env.production
sed -i "/^PAPERCLIP_SECRET=$/ s/$/`rake secret`/" .env.production
sed -i "/^SECRET_KEY_BASE=$/ s/$/`rake secret`/" .env.production
sed -i "/^OTP_SECRET=$/ s/$/`rake secret`/" .env.production

vim .env.production
#Redis,Postgresql,言語,SMTP,S3の設定

RAILS_ENV=production bundle exec rails db:setup
RAILS_ENV=production bundle exec rails assets:precompile

exit

cat << "_EOF_" > /etc/systemd/system/mastodon-web.service
[Unit]
Description=mastodon-web
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="PORT=3000"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_


cat << "_EOF_" > /etc/systemd/system/mastodon-sidekiq.service
[Unit]
Description=mastodon-sidekiq
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="DB_POOL=5"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

cat << "_EOF_" > /etc/systemd/system/mastodon-streaming.service
[Unit]
Description=mastodon-streaming
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="NODE_ENV=production"
Environment="PORT=4000"
ExecStart=/usr/bin/npm run start
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

systemctl enable mastodon-{web,sidekiq,streaming}
systemctl start mastodon-{web,sidekiq,streaming}

cat << "_EOF_" | crontab -
RAILS_ENV=production
@daily cd /home/mastodon/live && /home/mastodon/.rbenv/shims/bundle exec rake mastodon:daily > /dev/null
_EOF_

yum -y install nginx

cat << "_EOF_" > /etc/nginx/conf.d/mastodon.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name {domainName};

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://localhost:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}
_EOF_

systemctl enable nginx
systemctl start nginx

# ユーザ登録後 admin設定
RAILS_ENV=production bundle exec rails mastodon:make_admin USERNAME={UserName}

メモ

EC2のDiskはSSDで(swapで使う)
ロードバランサーはApplicationの方じゃないとwebSocketがうまくいかない
コミュニティベースのシステムだからCloudFrontはあまり必要性感じなかったので使わなかった
(日本向けだしS3のバケット東京リージョンにあるし、S3もかなり性能いいし)
もしCloudFrontを使うなら、websocketできないからS3の前に置く感じ
今回CloudFrontの利点があるとすれば”ドメイン”が自分の使えることぐらいかな
CentOSじゃなくてAmazonLinux使いたかったけど、ffmpeg入れるのにやたら時間かかったからやめた。一応動いたけど(純正AWSが。。。)
DockerはDeployまで楽そうだけど、効率よくなさそうだったのでやめた
AWSでDocker使うならECSでやってみたいけど、Mastodonはすんなりできるのかなー
セキュリティ的にはロードバランサーからの80番ポートしか受け付けないように制御してるから大丈夫かな。
sshでのログインは同じVPC内に踏み台サーバ立ててと。

最後に

ここまで読んで頂きありがとうございます。
技術的なことを投稿するのはこれが初めてですが、だれかのお役にたてれたら嬉しいです。
普段はPHPとAWS少しいじる程度なのでいい勉強になりました。
ほとんど公開されている文献をもとにプラモデル感覚で作りましたので、ご指摘等あればコメント頂ければと思います。
個人でのサイト運用となりますので、落ちたらごめんなさい。

続きを読む

Rails4+Capistrano3+Nginx+Unicorn EC2へのデプロイ < 後編 >

インフラ勉強中の者がAWSにrailsアプリをEC2にデプロイしたので備忘録として残します。
< 後編 >です。

【イメージ】
スクリーンショット 2017-05-09 11.04.30.png

< 前編 >Unicorn の設定までを実施しました。

デプロイチェック

ローカル
$ bundle exec cap production deploy:check

デプロイチェックをすると下記のようなエラーが出るはずです。

~ 省略 ~
00:02 deploy:check:linked_files
ERROR linked file /var/www/deployApp/shared/config/database.yml does not exist on YOUR_IP_ADDRESS

/var/www/deployApp/shared/config/database.yml が存在しないためのエラーです。

shared ディレクトリは、Capistranoが自動生成するディレクトリです。
EC2の qiitaApp の配下を確認します。

EC2
[myname@ip-10-0-1-86 qiitaApp]$ ls -la
drwxrwxr-x 4 ec2-user ec2-user 4096  5  9 04:08 .
drwxrwxrwx 3 root     root     4096  5  9 04:06 ..
-rw-rw-r-- 1 ec2-user ec2-user    6  5  9 03:33 .ruby-version
drwxrwxr-x 2 ec2-user ec2-user 4096  5  9 04:08 releases ← 自動生成
drwxrwxr-x 7 ec2-user ec2-user 4096  5  9 04:08 shared ← 自動生成

config 配下に database.yml を生成すればエラーは出なくなるのですが、次に同じようにsecrets.ymlが存在しないというエラーが出ますので、共に生成して変更します。

EC2上にdatabase.ymlの生成の前にローカルの database.yml を変更します。

ローカル
$ vi config/database.yml 

======================= database.yml ================================

~ 省略 ~
#production:
#  <<: *default
#  database: db/production.sqlite3
   ↓↓↓
production:
  <<: *default
  adapter: mysql2
  database: qiita_db
  host: qiita_db.xxxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com                                                                                                                                                     
  username: qiita_user
  password: YOUR_DB_PASS
  encoding: utf8

====================================================================

変更したら database.yml 全体をコピーして保存します。

EC2
[ec2-user@ip-10-0-1-86 qiitaApp]$ cd shared/config/
[ec2-user@ip-10-0-1-86 config]$ touch database.yml
[ec2-user@ip-10-0-1-86 config]$ vi database.yml

======================= database.yml ===============================

 ローカルでコピーしたdatabase.ymlを貼り付けます。

====================================================================

同様に secrets.yml もローカルの中身をコピーして(変更はなし)、configの配下に secrets.yml を生成して貼り付けます。

EC2
[ec2-user@ip-10-0-1-86 config]$ touch secrets.yml
[ec2-user@ip-10-0-1-86 config]$ vi secrets.yml

再度、デプロイチェックを実施します。

ローカル
$ bundle exec cap production deploy:check

エラーが出なければdeploy:checkは完了です。

デプロイ

ローカル
$ bundle exec cap production deploy --trace #traceをつけることでバグの箇所を発見しやすくします

エラーが出なければデプロイ完了です。

今回は、scaffoldで簡単なUserアプリを生成しているので、 直接、http://IP_ADDRESS/users/ を叩いて確認してみます。

…おそらく500エラーが返ってきてしまって確認できないと思います。

Unicornの状態とエラーログを確認します。

EC2
[ec2-user@ip-10-0-1-86 config]$ ps ax | grep unicorn
30256 ?        Sl     0:01 unicorn master -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                             
30311 ?        Sl     0:00 unicorn worker[0] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30313 ?        Sl     0:00 unicorn worker[1] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30316 ?        Sl     0:00 unicorn worker[2] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30334 pts/1    S+     0:00 grep --color=auto unicorn

Unicornは稼働していることが確認できます。
次はエラーログです。

EC2
[ec2-user@ip-10-0-1-86 config]$ cd ../..
[ec2-user@ip-10-0-1-86 qiitaApp]$ tail -f current/log/unicorn_error.log 

この状態で再度ローカルからデプロイします。

ローカル
[ec2-user@ip-10-0-1-86 qiitaApp]$ bundle exec cap production deploy
EC2
I, [2017-05-09T06:57:08.516736 #30256]  INFO -- : executing ["/var/www/qiitaApp/shared/bundle/ruby/2.3.0/bin/unicorn", "-c", "/var/www/qiitaApp/current/config/unicorn.rb", "-E", "deployment", "-D", {8=>#<Kgio::UNIXServer:fd 8>}] (in /var/www/qiitaApp/releases/20170509065649)
I, [2017-05-09T06:57:08.735844 #30256]  INFO -- : inherited addr=/tmp/unicorn.sock fd=8
I, [2017-05-09T06:57:08.736108 #30256]  INFO -- : Refreshing Gem list
I, [2017-05-09T06:57:09.978667 #30311]  INFO -- : worker=0 ready
I, [2017-05-09T06:57:09.981219 #30256]  INFO -- : master process ready
I, [2017-05-09T06:57:09.982336 #30313]  INFO -- : worker=1 ready
I, [2017-05-09T06:57:09.985331 #30316]  INFO -- : worker=2 ready
I, [2017-05-09T06:57:10.155786 #29111]  INFO -- : reaped #<Process::Status: pid 29166 exit 0> worker=0
I, [2017-05-09T06:57:10.155874 #29111]  INFO -- : reaped #<Process::Status: pid 29168 exit 0> worker=1
I, [2017-05-09T06:57:10.155945 #29111]  INFO -- : reaped #<Process::Status: pid 29171 exit 0> worker=2
I, [2017-05-09T06:57:10.155973 #29111]  INFO -- : master complete

この時点ではエラーらしきものが出ていません。

再度 http://IP_ADDRESS/users/ を叩いて確認してみます。

EC2
E, [2017-05-09T06:59:27.508190 #30316] ERROR -- : app error: Missing `secret_token` and `secret_key_base` for 'production' environment, set these values in `config/secrets.yml` (RuntimeError)

Missing secret_token and secret_key_base とあります。
EC2上の qiitaApp/current/config/secrets.yml の secret_key_base:の環境変数(SECRET_KEY_BASE)が設定されていないことが原因です。

EC2
[ec2-user@ip-10-0-1-86 qiitaApp]$ cd current
[ec2-user@ip-10-0-1-86 current]$ bundle exec rake secret
1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz ←コピーする
[ec2-user@ip-10-0-1-86 current]$ vi config/secrets.yml 

============================== secrets.yml ==================================

~ 省略 ~
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
          ↓↓↓
  secret_key_base: 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz

=============================================================================

本来であれば、直接入力するのではなく、

SECRET_KEY_BASE=1234567890abcd...
export SECRET_KEY_BASE

とかで環境変数のまま使用すべきです。
が、何回実施してもググってもうまくいかなかったので、取り急ぎ直接入力で対応します。
dotenvというgemがあるようなので、それで対応してもいいと思います。

再度、ローカルからデプロイして、urlを叩けば下記の画面が表示されると思います。
スクリーンショット 2017-05-09 16.28.33.png

大変参考にさせていただきました。
capistranoを使ってrailsをnginx+unicorn+mysqlの環境にデプロイする
ローカルで開発したRailsアプリをCapistrano3でEC2にデプロイする
Capistrano3でUnicorn+Nginxな環境にRailsをデプロイする:初心者向け
Railsプロダクション環境 Unicornでsecret_key_baseが設定されていないとエラーが出る

続きを読む

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

続きを読む

lambda入門(Node)③ – API Gatewayを使ってslackからのリクエストをlambdaで受けられるようにする

第3回になりました。

過去のはこちら。

今回は、ようやくやりたいことに近づいて来まして
slackからのリクエストをlambdaで受けられるようにしたいと思います。

何か調べていくと、どうもAPI Gatewayを使うと良い感じぽい。
まずはAPI Gatewayについて予習を。

API Gateway

どんな役割をしてくれるのか

APIのエンドポイントとして待ち構える玄関として使える
現在はhoge/*のようなパスを/hoge/{proxy+}として設定できる
プロキシリソースなるものらしい
post, putの振り分けなどが簡単になった

課金体系

受信した API 呼び出しと、送出したデータ量に対して発生

serverless frameworkでの設定

serverless.yml
functions:
  bookStore:
    handler: books/store.store
    events:
      - http:
          path: books
          method: get
          cors: true

これだけ。
デプロイ実行すると

$ serverless deploy -v --stage dev
Serverless: Packaging service...
・
・
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::Deployment - ApiGatewayDeployment1490662361327
・
・
Service Information
service: testProject
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  GET - https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/books
functions:
  bookStore: testProject-dev-bookStore

エンドポイントが作られた。。!

レスポンスを送れるか試してみる

$ curl https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/books
{"message":"ok",...........}

うん、okそう、すごい。ymlに書くだけで何でも設定してくれちゃう。

Slackから応答させるようにする

ここで今回の本題。
作成したエンドポイントに対してスラックがリクエストを送って
slack上にメッセージが返ってくるようにする。

全体像

スクリーンショット 2017-04-29 15.17.57.png

やることとしては、リクエストパラメータを解析してデータを保存。
保存した結果を返す

book.js

'use strict';

const slackAuthorizer = require('../authorizer/slackAuthorizer');
const parser = require('../service/queryParser');

const bookSave = require('../useCase/book/save.js');

module.exports.book = (event, context, callback) => {
  const queryParser = new parser(event.body);
  const authorizer = new slackAuthorizer(queryParser.parseToken());

  /**
   * 認証
   */
  if (!authorizer.authorize()) {
    context.done('Unauthorized');
  }

  /**
   * @Todo ここで lambda function の振り分けを行いたい
   */
  bookSave(event, (error, result) => {});

  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'ok',
    }),
  };

  callback(null, response);
};

同期で処理を行いたいので asyncを使って処理をブロックごとに実行する

save.js

'use strict';

const AWS = require('aws-sdk');
const dynamoDB = new AWS.DynamoDB.DocumentClient();
const bookTable = process.env.bookTable;

const uuidV1 = require('uuid/v1');

const webhookUrl = process.env.slack_webhook_url;
const request = require('request');

const dateTime = require('node-datetime');
const dt = dateTime.create();
const insertDate = dt.format('Y-m-d H:M:S');

const async = require('async');

const parser = require('../../service/queryParser');

module.exports = (event, callback) => {

  const queryParser = new parser(event.body);

  const key = uuidV1();

  async.series([
    function(callback) {

      /**
       * データを保存
       */
      dynamoDB.put({
        'TableName': bookTable,
        'Item': {
          'id': key,
          'title': queryParser.parseText(),
          'insert_date': insertDate,
        },
      }, function(err, data) {
        callback(null, "saved");
      });
    },
    function(callback) {

      /**
       * 保存したものを取り出して
       * 結果を返す
       */
      dynamoDB.get({
        TableName: bookTable,
        Key: {
          id: key,
        },
      }, function(err, data) {
        if (!err) {
          const response = {
            text: ``${data.Item.title}` is Saved !!`,
          };
        
          /**
           * webhook でチャンネルにメッセージを返す
           */
          request.post(webhookUrl, {
            form: {
              payload: JSON.stringify(response),
            },
          }, (err, response, body) => {
            callback(null, 'getData');
          });
        }
      });
    },
  ], function(err, results) {
    if (err) {
      throw err;
    }
  });
};

slash commandから送られてきたパラメータをパースするために
query-stringモジュールを使い、ラップした

queryParser.js
'use strict';

const queryStringParser = require('query-string');

module.exports = class queryParser {

  constructor (queryString) {
    this.queryString = queryString;
  }

  parseToken () {
    return queryStringParser.parse(this.queryString).token;
  }

  parseText () {
    return queryStringParser.parse(this.queryString).text;
  }
};

slashコマンドの設定は省略します。
最初の api gatewayで設定されたエンドポイントのURLを
設定してあげればいいので

実際に動かすとこんな感じです。

4b4b9687608f58545ef2b4536a7166c2.gif

ソースは汚いかもしれませんが、saverlessフレームワークで
エンドポイント作成やdb、外部サービスの連携が簡単に出来ました

もっとキレイに書けるようにjsの筋力をつけていかねば。。笑

続きを読む

JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (2)

この記事では、複数のDockerコンテナで構成される環境を構築するための設定例を具体的に解説します。ワークショップのフォローアップではありますが、一般的な事柄を扱いますので、ワークショップに参加されていない方にもお読み頂ける内容です。

前回の記事: JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (1)

春ですね

こんにちは。Emotion Techの子安です。ようやく暖かくなってきましたね。やっぱり春といえば桜ですかね!

sakura.jpg

恐縮です。

前回のつづき

さて、前回はテーマとする環境の構成を説明し、利用するコンテナ定義ファイル docker-compose.yml の枠組みを解説しました。更に web app db コンテナのうち、 web db コンテナの設定を見ました。今回はアプリケーションそのものである app コンテナを見ていきます。

各コンテナの設定 2

appコンテナ

app:
  image: phusion/passenger-ruby23:0.9.20
  environment:
    APPLICATION_ENV: development
    APPLICATION_ROLE: app
    PASSENGER_APP_ENV: development
  labels:
    eb.workshop.role: app
  networks:
    - eb.workshop
  depends_on:
    - db
  volumes:
    - ./app/init/40_setup.sh:/etc/my_init.d/40_setup.sh:ro
    - ./app/passenger/passenger.conf:/etc/nginx/sites-available/default:ro
    - ./app/rails-app:/var/www/rails-app

Ruby on Railsのアプリを実行するappコンテナです。phusion/passenger-dockerイメージを利用しています。このイメージはさまざまな機能を持っていますが、まずは設定内容を見ましょう。

  • image: phusion/passenger-dockerイメージを指定しています。
  • environment: イメージ側で PASSENGER_APP_ENV 環境変数を読んでRailsの環境を切り替えてくれる機能があります。
  • volumes: ここで3つのマウントを指定しています。コンテナ側のパスを見てください。

    • /etc/my_init.d/40_setup.sh:ro: /etc/my_init.d 配下に置いたスクリプトを初期化時に実行してくれる機能があります。実行権限を忘れずにつけるようにします。 :ro は読み取り専用の意味です。
    • /etc/nginx/sites-available/default:ro: ここにnginxの設定ファイルが置かれています。
    • /var/www/rails-app: Railsのアプリケーションをここに置きます。これは任意の場所で構いません。上記のnginx設定ファイルに記述しています。

phusion/passenger-docker

phusion/passenger-dockerは、Phusion社がメンテナンスしているDockerイメージです。このイメージは、さらに別のイメージ phusion/baseimage-docker をベースとして作られています。このphusion/baseimage-dockerには、いくつかの有用な機能が含まれています。

  • 正しいinitプロセス: /sbin/my_init にinitプロセスを内蔵しています。本来initプロセスには、親プロセスのいなくなった子プロセス(孤児)を里親として引き受ける役割があります。加えて SIGTERM を受け取った際に、他のサービスを終了させる機能を持ちます。
  • スーパーバイザ: 軽量なスーパーバイザ runit を内蔵しています。
  • ログマネージャ: syslogデーモン syslog-ng を内蔵しています。logrotateも設定済みです。
  • cron: cronデーモンを内蔵しています。

重要なことは、これらの機能が協調動作するように設定されていることです。 /sbin/my_init がrunitを立ち上げ、runitがsyslog-ngやcronを管理します。デフォルトのコンテナ起動コマンドが /sbin/my_init に設定されていますので、

docker run phusion/baseimage

として起動することで、最小限のコンテナ化されたLinux(Ubuntu)環境を手に入れることができます。他にも、今回も利用している /etc/my_init.d 配下のスクリプトを初期化時に実行する機能、root以外でコマンドを実行するための setuser コマンドなど、様々な機能や仕組みを備えています。詳しくはWebページを参照ください。

phusion/passenger-dockerは、このphusion/baseimage-dockerの上に、nginxやrubyのアプリケーションサーバである Phusion Passenger をインストールしてあります。nginxはrunitの配下に設定されていますので、すぐにスーパーバイズされたデーモンとして稼働させることができます。

コンテナ初期化スクリプト

可能なことはコンテナ自身に判断させるのが良い設計です。そのためにコンテナ起動時に実行するスクリプトを組み込むことがよく行われます。今回 app コンテナで設定している初期化スクリプトの内容を見てみましょう。 /app/init/40_setup.sh を参照ください。

このスクリプトの主な役割は4つです。

  • nginxを起動可能にする
  • Railsアプリのセットアップ
  • 環境変数の引き渡し
  • DBマイグレーション

ハンズオンのため、本来はコンテナ初期化スクリプトに記述すべきでない処理もここに入っていますが、続けて詳しく説明します。

/app/init/40_setup.sh
rm -f /etc/service/nginx/down

これはrunitの機能です。サービスを定義するディレクトリ(今回は /etc/service/nginx )の直下に down という名前のファイルがあると、自動起動しません。phusion/passenger-dockerイメージ側で配置されているものですが、自動起動してほしいのでファイルを削除しています。

/app/init/40_setup.sh
chown -R app:app /var/www
cd /var/www/rails-app
setuser app bundle install --path=../vendor/bundle
if [ "$PASSENGER_APP_ENV" = 'production' ]; then
  setuser app bin/rails assets:precompile
  setuser app bundle install --path=../vendor/bundle --without test development --deployment --clean
fi

このあたりは実環境であればCIで、ビルドの一処理として適切に実施すべきものです。アプリケーションのファイルパーミッションを適切に設定し、ライブラリのインストールを行います。更にproductionモードであれば、アセットのプリコンパイルと不要なライブラリの削除を行います。

/app/init/40_setup.sh
echo "passenger_env_var 'SECRET_KEY_BASE' '$SECRET_KEY_BASE';" >> /etc/nginx/conf.d/10_setenv.conf
echo "passenger_env_var 'RDS_HOSTNAME' '$RDS_HOSTNAME';" >> /etc/nginx/conf.d/10_setenv.conf

ここは環境変数をpassengerへ引き渡すための処理です。コンテナでは環境変数を扱う機会が多くなりますので、利用するミドルウェアに合わせて設定が必要です。

/app/init/40_setup.sh
RAILS_ENV=$PASSENGER_APP_ENV setuser app bin/rails db:create db:migrate

こちらはDBのマイグレーションです。この処理も実環境であればコンテナ初期化スクリプトではなく、デプロイの手順の一貫として実施すべきものです。

次回へ

ここまでが app コンテナの設定でした。長くなりましたので、つづきは次回にしたいと思います。次回はElastic Beanstalkの設定ファイル、Dockerrun.aws.jsonを解説する予定です。

続きを読む

Mastodonでgit pullしたらビルドエラー

概要

EC2で稼働させているMastodongit pullした後にdocker-compose buildしたら下記エラーが出てハマった。

環境

  • AWS EC2(t2.medium)
  • Ubuntu Server 16.04 LTS

エラー内容

ubuntu@ip-***-***-**-**:~/mastodon$ docker-compose build
redis uses an image, skipping
db uses an image, skipping
Building streaming
Step 1/9 : FROM ruby:2.4.1-alpine
 ---> 5eadd5d1419a
Step 2/9 : LABEL maintainer "https://github.com/tootsuite/mastodon" description "A GNU Social-compatible microblogging server"
 ---> Using cache
 ---> 95a4b711ef32
Step 3/9 : ENV RAILS_ENV production NODE_ENV production
 ---> Using cache
 ---> 499e95f00e13
Step 4/9 : EXPOSE 3000 4000
 ---> Using cache
 ---> 167a91f421f4
Step 5/9 : WORKDIR /mastodon
 ---> Using cache
 ---> e185ae07f027
Step 6/9 : COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
 ---> Using cache
 ---> 460d49537428
Step 7/9 : RUN BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs     libpq     libxml2     libxslt     ffmpeg     file     imagemagick  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*
 ---> Running in 68a8d45af6e8
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/main: No such file or directory
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/community: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
OK: 21 MiB in 29 packages
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
ERROR: unsatisfiable constraints:
  build-base (missing):
    required by: world[build-base]
  ffmpeg (missing):
    required by: world[ffmpeg]
  file (missing):
    required by: world[file]
  imagemagick (missing):
    required by: world[imagemagick]
  libpq (missing):
    required by: world[libpq]
  libxml2 (missing):
    required by: world[libxml2]
  libxml2-dev (missing):
    required by: world[libxml2-dev]
  libxslt (missing):
    required by: world[libxslt]
  libxslt-dev (missing):
    required by: world[libxslt-dev]
  nodejs (missing):
    required by: world[nodejs]
  postgresql-dev (missing):
    required by: world[postgresql-dev]
ERROR: Service 'streaming' failed to build: The command '/bin/sh -c BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs     libpq     libxml2     libxslt     ffmpeg     file     imagemagick  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*' returned a non-zero code: 11

解決方法

$ git checkout $(git describe --tags `git rev-list --tags --max-count=1`)

ハマってる最中にREADMEに安定バージョン使ってねって追記されてましたとさ。。

追記

v1.2にしたところ下記のエラーが再現。調査中。

ubuntu@ip-***-**-**-***:~/mastodon$ docker-compose build
redis uses an image, skipping
db uses an image, skipping
Building streaming
Step 1/9 : FROM ruby:2.4.1-alpine
 ---> 5eadd5d1419a
Step 2/9 : LABEL maintainer "https://github.com/tootsuite/mastodon" description "A GNU Social-compatible microblogging server"
 ---> Using cache
 ---> 95a4b711ef32
Step 3/9 : ENV RAILS_ENV production NODE_ENV production
 ---> Using cache
 ---> 499e95f00e13
Step 4/9 : EXPOSE 3000 4000
 ---> Using cache
 ---> 167a91f421f4
Step 5/9 : WORKDIR /mastodon
 ---> Using cache
 ---> e185ae07f027
Step 6/9 : COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
 ---> Using cache
 ---> 06b33c54cfd4
Step 7/9 : RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories  && BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs@edge     nodejs-npm@edge     libpq     libxml2     libxslt     ffmpeg     file     imagemagick@edge  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*
 ---> Running in 0b9005a839d9
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/main: No such file or directory
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/community: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
fetch https://nl.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz
140162439957356:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:s23_clnt.c:794:
ERROR: https://nl.alpinelinux.org/alpine/edge/main: Permission denied
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
OK: 21 MiB in 29 packages
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
WARNING: The repository tag for world dependency 'nodejs@edge' does not exist
WARNING: The repository tag for world dependency 'nodejs-npm@edge' does not exist
WARNING: The repository tag for world dependency 'imagemagick@edge' does not exist
ERROR: Not committing changes due to missing repository tags. Use --force to override.
ERROR: Service 'streaming' failed to build: The command '/bin/sh -c echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories  && BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs@edge     nodejs-npm@edge     libpq     libxml2     libxslt     ffmpeg     file     imagemagick@edge  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*' returned a non-zero code: 255

続きを読む

AWS CodeDeploy 導入調査 アプリ側編

AWS CodeDeploy 導入調査 AWS 設定編 の続きになります

前回のあらすじ

  • EC2 インスタンスを作った
  • コードアップロード用の S3 Bucket を作った
  • デプロイ用の IAM ロールを作った
  • CodeDeploy アプリケーションを作った

今回はアプリ側の作業です.

デプロイ先アプリ構成

Nginx – uWsgi を sudpervisord でプロセス監視
Nginx とか supervisord とかは割とどうでもいいのですが
サーバー構成はあらかじめ一通り作成しておきます.

さらに CodeDeloy-Agent をインストールしておきます.
http://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations-install.html

とりあえずデプロイしてみる

ローカル -> S3 のアップロードは AWS-CLI を使用します.
ローカルプロジェクトのトップディレクトリで,

$ aws deploy push --application-name <<app-name>> --s3-location s3://<<s3bucket-name>>/<<app-name>>.zip --source ./ --region <<region-name>>

を実行します.
<<app-name>> は CodeDeploy で作成したアップケーション名です.

  • –ignore-hidden-files オプションで隠しファイルを無視します.
  • S3 にアップロードするファイル名は本当はなんでもいいです.
  • 圧縮時の拡張子は tar.gz でもいけるはず.

アップロードに成功すると以下のように出力されます.

To deploy with this revision, run:
aws deploy create-deployment --application-name <<app-name>> --s3-location bucket=<<s3bucket-name>>,key=<<app-name>>.zip,bundleType=zip,eTag=<<eTag>>--deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>

ただし今回は成功しないと思います.

appspec.yml の作成

appspec.yml は CodeDeploy によるデプロイ作業を記述したものになります.
まずローカルプロジェクトのトップディレクトリに appspec.yml ファイルを作成し,

version: 0.0
os: linux

まで記述します. ここで,
– version は appspec のシステムバージョンで2017/04現在 0.0 のみ有効です
– os はデプロイ先 EC2 インスタンスの OS 種類で windows か linux かを設定します. linux ディストリビューションまで指定する必要はありません.

次に,

files:
   - source: /
     destination: /usr/local/src

でファイル配置を設定します.
source はコードパスで appspec.yml があるディレクリの相対パスになります.
なので source: / とした場合はディレクリ構成そのままで EC2 インスタンスの
destination に配置されます.
特定のディレクリ/ファイルのみ配置先を変えたい場合は – source: destination の記述を追加することができます.

さらに, 今回は以下の設定を追加しました.

hooks:
  AfterInstall:
    - location: copy_settings.sh
      timeout: 180
      runas: root
    - location: pip_install.sh
      timeout: 180
      runas: root
  ApplicationStart:
    - location: reload_uwsgi.sh
      timeout: 180
      runas: root

hooks 以下では CodeDeploy ライフサイクルの各処理にフックしてスクリプトを実行させることができます.
http://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html

CodeDeploy-Agent は Ruby で記述されており, Ruby コード上で Shell スクリプトを実行します.
今回追加した処理は

  • コード配置後に EC2インスタンス内に仕込んでおいた追加設定をコピー
  • プロジェクト内の requirements.txt の内容で pip install
  • アプリスタート時に uwsgi のリスタート

です. runas は各スクリプトの実行ユーザーです.
今回は面倒だったので全て root にしました.

デプロイに再度チャレンジ

ふたたび ローカル -> S3 のアップロード をやってみると今度は成功するはずです.
アップロードが成功したら次は AWS コンソールの CodeDeploy に移動します.

  1. ダッシュボードでアプリケーションを選択して “アクション” -> “新しいリビジョンのデプロイ”
  2. “アプリケーション”, “デプロイグループ” はそのまま, “リビジョンタイプ” は Amazon S3
  3. “リビジョンの場所” でアップロードしたソースコードを指定します.
  4. その他はそのままで “デプロイ” でデプロイ開始します.

フックスクリプトでエラーにならなければデプロイは成功するはずです.

フックスクリプトでエラーになったので解決した内容は次回

続きを読む

AWS Transit VPCソリューションを使って 手軽に マルチリージョン間 VPN環境を構築してみる

Transit VPC とは

AWS の VPC 間 VPN 接続を自動化するソリューションです。
高度なネットワークの知識がなくても、超カンタンに、世界中の リージョンにまたがる VPC を接続するネットワークを構築することができます。

概要


ある VPC を転送専用の VPN HUB(Transit VPC)として作成することで、他のVPCからのVPN接続を終端し、スター型のトポロジを自動作成します。
利用している AWSのアカウントに紐づいた VPC で VGW を作成すれば、HUBルーターに自動的に設定が投入され、VPNの接続が完了します。

予めテンプレートが用意されているので、ルーターの細かい設定を行う必要がありません。

また、高度な設定が必要な場合も、Cisco CSR1000V を利用しているので、Cisco IOS の機能は自由に利用することも可能です。

Adobe社では、大規模なネットワークを、AWS上の CSR1000V で構築しています。
https://blogs.cisco.com/enterprise/adobe-uses-cisco-and-aws-to-help-deliver-rich-digital-experiences

仕組み

  • AWS CloudFormation を使って、Transit VPC の CSR1000v のプロビジョニングが非常に簡単に行えるようになっています。

  1. VGW Poller という Lambda ファンクションが用意されており、Amazon CloudWatch を使って、Transit VPC に接続すべき VGW が存在するかを常に確認しています。
  2. VGWが存在する場合、VGW Poller が必要な設定を行い、設定情報を Amazon S3 bucket に格納します
  3. Cisco Configurator という名前の Lambda ファンクションが用意されており、格納した設定情報から、CSR1000v 用の設定を作成します
  4. Cisco Configurator が Transit VPC の CSR1000v へ SSH でログインし設定を流し込みます。
  5. VPN接続が完了します。

詳細については、下記、ご参照ください。
– AWS ソリューション – Transit VPC
https://aws.amazon.com/jp/blogs/news/aws-solution-transit-vpc/
– Transit Network VPC (Cisco CSR)
https://docs.aws.amazon.com/solutions/latest/cisco-based-transit-vpc/welcome.html

Transit VPC 構築方法

基本は、Wizard に従い、3(+1)ステップで設定が可能です。

事前準備 – CSR1000V用 Key Pair の作成

CSR1000V へログインする際に使う、Key Pair を EC2 で一つ作っておきましょう。
EC2 > Network & Security > Key Pairs から作成できます。名前はなんでもいいです。

.pem ファイルが手に入るので、後ほど、CSR1000V へログインする際にはこれを使います。

Step 1. Cisco CSR1000v ソフトウェア利用条件に同意

AWS マーケットプレイスの下記のページから、リージョンごとの値段が確認できます。

https://aws.amazon.com/marketplace/pp/B01IAFXXVO

  • For Region で 好きなリージョンを選択
  • Delivery Method で Transit Network VPC with the CSR 1000v を選択

その後 Continue

  • Manual Launch タブが選択されていること
  • Region / Deployment Options が先に設定したものとなっていること

上記を確認し、Accept Software Terms をクリックします。

Step 2. Transit VPC スタックの起動

下記のページより、Transit VPC 用の CloudFormation を起動することで、Transit VPC に必要な CSR1000v をセットアップすることができます。

http://docs.aws.amazon.com/ja_jp/solutions/latest/cisco-based-transit-vpc/step2.html

上記ページから Launch Solution をクリックします。
CloudFormation のページが立ち上がるので、まず、VPN HUB を 設置するリージョンになっているか確認します。
このソリューションは、Lambda を使うので、Transit VPC を設置できるのは、Lambda が使えるリージョンに限ります。東京は対応してます。
Lambda 対応リージョンは、こちらで確認できます。
https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/

正しいリージョンになっていたら、設定を進めます。

2-1. Select Template

テンプレートがすでに選択された状態になっていると思うので、特に設定の必要なし

Next をクリック

2-2. Specify Details

幾つかのパラメーターがありますが、最低限下記を変更すれば、動作します。

  • Specify Details

    • Stack name を設定します。
  • Parameters
    - Cisco CSR Configuration

    • SSH Key to access CSR で、事前準備で作成した key を選択します。
    • License Modelで、BYOL (Bring Your Own License) もしくは License Included を選択します。

CSR1000v のライセンスについて、Cisco から購入したものを使う場合は、BYOL を選択します。
ライセンス費用も利用料に含まれた利用形式が良ければ、License included version を選択します。
検証ライセンスがあるので、テストの場合は、BYOL で良いかと思います

入力が完了したら、 Next

2-3. Options

特に設定の必要なし Next をクリック

2-4. Review

  • 設定を確認して、一番下の acknowledge のチェックボックスをクリック

その後、Create で CSR1000V のインスタンス作成・セットアップが始まります

5分ほどすると、CSR1000V が 2インスタンス 立ち上がります。

事後設定(オプション)

起動した CSR1000V は デフォルトで、Lambda ファンクションからのログインのみを許可する設定になっているので、ssh で CLI にアクセスしたい場合は、Security Group の設定を変更します。

EC2 のダッシュボードから CSR1000V を選択し、Security Group の設定で SSH を許可します。
すべてのPCからSSHを受ける場合は、ソースアドレスとして 0.0.0.0/0 を指定します。

設定後、ssh でログインするには、事前準備で作成した キーペアで取得した .pem ファイルのパーミッションを変更します。

chmod 400 <キーペア名>.pem

これを使って、下記のコマンドで CSR1000V にログインできます

ssh -i <キーペア名>.pem <CSR1000V のパブリックIP>

ログインすれば、CSR1000v であることがわかると思います。

ip-XXX-XXX-XXX-XXX #sh ver
Cisco IOS XE Software, Version 16.03.01a
Cisco IOS Software [Denali], CSR1000V Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.3.1a, RELEASE SOFTWARE (fc4)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2016 by Cisco Systems, Inc.
Compiled Fri 30-Sep-16 02:53 by mcpre

<省略>

Step 3. スポーク VPC のタグ付け

上記、CSR1000V が立ち上がれば、あとは、Spoke VPC の VGW でタグを付けるだけで、CSR1000V への VPN 接続が完了します。

3-1. Spoke VPC の作成

新たに Spoke VPC を作成する場合は、下記の手順で作ります。

  • 好きなリージョンに、VPC を作成します。

    • VPC Dashboard > Virtual Private Cloud > Your VPC > Create VPC で作成します。
  • Virtual Private Gateway(VPG) を作成します。
    • VPC Dashboard > VPN Connection > Virtual Private Gateways > Create Virtual Private Gateway で作成します。
  • 作成した VPG を、先程作成した VPCにアタッチします。
    • VPC Dashboard > VPN Connection > Virtual Private Gateways > Attach to VPC  から、VPCへVGWをアタッチします。

3-2. VGW のタグ付け

  • 先程作成した VGW を選択し、下部の Tags タブを選択します。

    • Edit をクリックし Tag を追加します。

デフォルトの設定では、下記のタグを利用する設定になっていますので、下記タグを追加します
Key = transitvpc:spoke
Value = true

上記の設定が完了すると、VGWと Step2. で作成した、2台の CSR1000Vの間で自動的に VPN 接続が確立します。

http://docs.aws.amazon.com/ja_jp/solutions/latest/cisco-based-transit-vpc/step3.html

Step 4. 他の AWS アカウントからの接続設定 (オプション)

http://docs.aws.amazon.com/ja_jp/solutions/latest/cisco-based-transit-vpc/step4.html

まとめ

AWS の Transit VPC を使えば、世界中 にまたがる AWS の 各リージョンのVPCを簡単に VPN 接続できます。

また、VPN HUB ルーターは Cisco CSR1000v なので、企業内のシスコルーターと VPN 接続すれば、DC を AWS にして、世界中にまたがる VPN ネットワークを構築することができます。
ダイレクトコネクトとも連携可能です。

参考

On board AWS TransitVPC with Cisco CSR1000v (英語ですが、手順がわかりやすいです)

Cisco CSR 1000v: Securely Extend your Apps to the Cloud
https://www.slideshare.net/AmazonWebServices/cisco-csr-1000v-securely-extend-your-apps-to-the-cloud

続きを読む

Travis CI + s3でデプロイしてみた。

Travis CIからs3にデプロイした時に、詰まったのでメモ。

Travis CIとは

「継続的インテグレーション(CI)」ツール。
あらかじめ指定したgithubのレポジトリに、コードをpushすると、自動でテストを行ってくれる。
テストが成功すれば指定したホスティングサービスにデプロイもしてくれる。失敗するとメール等で通知してくれる、テスト自動化ツール。

手順

前提条件

・GitHub のアカウントを持っている
・GitHub にリポジトリを持っている
・Travis CI のアカウントを持っている
・Travis CI の CLI をインストールしている (gem install travis)
・AWS のアカウントを持っている
・s3の更新権限を持ったアクセスIDとSecrete keyをIAMで作っている。
・s3のバケットを、静的サイトをしてホスティングしている。

1.https://travis-ci.org/ にログインして当該リポジトリをONにする。
2.ローカルのプロジェクトフォルダで、$ touch .travis.yml
3.$Travis encrypt "アクセスキーID"$Travis encrypt "シークレットアクセスキー"で、ターミナルに出てきた、secure: "..."を.travis.ymlに貼り付ける。

travis.yml
language: node_js
script: echo "do nothing"
deploy:
  provider: s3
  access_key_id:
    secure: "アクセスキーID"
  secret_access_key:
    secure: "シークレットアクセスキー"
  bucket: s3-website.shikumi-zukuri.com
  region: us-east-1
  endpoint: s3.amazonaws.com
  on:
    branch: master

4.git add .git commitgit pushする。

これで、Travis CIが、自動でテストを実行してくれ指定したs3バケットに、htmlとかのファイルをデプロイしてくれる。

参考
公式ドキュメント
Travis CIs3 Deployment
Travis CI for Complete Beginners

その他
# travis-ciからAmazon S3へファイルをデプロイする
GitHub + Travis CI + Amazon S3 で git push で更新されるサイトをつくった

サンプルコード(github)

続きを読む