AWS Route53からさくらにドメインを移すまで。

とりあえず、やっと解決

AWSでクラウド死したのでさくらへの引越ししてました。そこそこ完了したので、ドメインの引越し手順をまとめます。
今回はさくらのVPSに繋ぐまで。さくらのサーバーとかクラウドだともう少し設定がいるのかな?
今回はAWSとさくらのIDは持ってる前提で

管理元からオースコードをもらう

ドメインを移管するのに当たって、まず、現在の管理者にオースコードを発行してもらう必要があります。
AWSのRoute 53からRegistered DomainにとんでAuthoriation Generateをクリック

名称未設定.png

そこで発行される英字と記号のランダムな文字列をコピーします

管理先にオースコードを渡す

さくらのページからドメイン転入をクリック
そこで一番下のトップレベルドメイン(.comとか.co.jp)の部分に合わせてドメイン転入手続きをクリック。

image.png

料金は年額です。Route53よりちょい安いかも。

その後はチュートリアルにしたがってドメイン名(www.hogedomein.comとかつかない奴ね)とオースコードを入力。
この時にオースコードを再発行しないように。これのおかげでさくらのサポセンに連絡するはめに

ドメインレジストラへの認証

ここら辺インターネットの仕組みが良く分かってないのですが、ドメインを管理する日本のエラい会社からメールが来ます。そこで宛先のアドレスを使ってログインし、承認をクリックします。
これで日本のエラい会社にドメインが認証されます。

ネームサーバーの編集

手続きが終了すると、持っているドメインがさくら管理になります。
そこで、自分のVPSに使えるように設定しないといけません

まずネームサーバーの設定を編集します
ドメインメニューからゾーン編集をクリック
image.png

AレコードをVPSのIPv4アドレスに変更します。
設定.png

編集したらデータの送信をクリック

Whois情報の設定

ここで見事にはまってました。
Whoisというドメイン自体の情報にここのネームサーバーでアドレス解決をしてと教えなければいけません。
ドメインメニューからwhois情報をクリック

引越し直後はネームサーバーの欄がAWSのネームサーバーになっているので、これをさくらのネームサーバーにします。

設定2.png

ネームサーバー1にns1.dns.ne.jpネームサーバー2にns2.dns.ne.jpを設定します。3,4は空きです。

完了!

これでドメイン側の設定は完了です。ネームサーバーの情報がインターネットのネットワークに範囲されるまで1日くらい時間がかかるので気長に。
サーバー側のファイアウォールを空けるのを忘れないでね。

これで安いそこそこの定額サーバーが動くぜ。

続きを読む

AnsibleでAWS操作 CloudFrontディストリビューション編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス編
  3. S3バケット編
  4. CloudFrontディストリビューション編

関連記事

aws-cli コマンド一覧(随時追記)

やりたかったこと

  • CloudFrontディストリビューションの作成
  • S3バケットとの連携
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

  • CIサーバー(ansible実行サーバー)構築済み
  • CLIサーバー(aws-cli実行サーバー)構築済み
  • Ansibleインストール済み
  • aws-cliインストール済み
  • 各サーバーへのSSH接続設定済み

${~}は各環境に合わせて値を設定してください。

作業フロー

1. CloudFrontディストリビューションを新規作成

command
ansible-playbook -i inventory/production create-aws-cf-distribution.yml

ディレクトリ構成


├── ansible.cfg
├── create-aws-cf-distribution.yml
├── files
│   └── production
│       └── cf
│           └── distribution.json
├── inventory
│   └── production
│       └── inventory
├── roles
│   └── create-aws-cf-distribution
│       └── tasks
│           └── main.yml
└── vars
    └── all.yml

Ansible構成ファイル

inventory

inventory/production/inventory
[ciservers]
${CIサーバーホスト}

[cliservers]
${CLIサーバーホスト}

[all:vars]
ENV=production

vars

vars/all.yml
AWS:
  S3:
    BUCKET:
      NAME: ${バケット名}

files

files/production/cf/distribution.json
{
  "Id": "",
  "IfMatch": "",
  "DistributionConfig": {
    "Comment": "For S3 Bucket.",
    "CacheBehaviors": {
      "Quantity": 0
    },
    "IsIPV6Enabled": true,
    "Logging": {
      "Bucket": "",
      "Prefix": "",
      "Enabled": false,
      "IncludeCookies": false
    },
    "WebACLId": "",
    "Origins": {
      "Items": [
        {
          "S3OriginConfig": {
            "OriginAccessIdentity": ""
          },
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "Id": "${一意となるID ※1}",
          "DomainName": "${S3バケットの静的サイトホスティングのエンドポイント}"
        }
      ],
      "Quantity": 1
    },
    "DefaultRootObject": "index.html",
    "PriceClass": "PriceClass_All",
    "Enabled": true,
    "DefaultCacheBehavior": {
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0
      },
      "LambdaFunctionAssociations": {
        "Quantity": 0
      },
      "TargetOriginId": "${※1で指定したID}",
      "ViewerProtocolPolicy": "allow-all",
      "ForwardedValues": {
        "Headers": {
          "Quantity": 0
        },
        "Cookies": {
          "Forward": "all"
        },
        "QueryStringCacheKeys": {
          "Quantity": 0
        },
        "QueryString": true
      },
      "MaxTTL": ${最大有効TTL},
      "SmoothStreaming": false,
      "DefaultTTL": ${デフォルトTTL},
      "AllowedMethods": {
        "Items": [
          "HEAD",
          "GET"
        ],
        "CachedMethods": {
          "Items": [
            "HEAD",
            "GET"
          ],
          "Quantity": 2
        },
        "Quantity": 2
      },
      "MinTTL": ${最小TTL},
      "Compress": false
    },
    "CallerReference": "${一意となる文字列}",
    "ViewerCertificate": {
      "CloudFrontDefaultCertificate": true,
      "MinimumProtocolVersion": "SSLv3",
      "CertificateSource": "cloudfront"
    },
    "CustomErrorResponses": {
      "Quantity": 0
    },
    "HttpVersion": "http2",
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0
      }
    },
    "Aliases": {
      "Items": [
        "${割り当てたいドメイン ※Route53にて紐付ける際に利用}"
      ],
      "Quantity": 1
    }
  }
}

playbook

create-aws-cf-distribution
- hosts: cliservers
  roles:
    - create-aws-cf-distribution
  vars_files:
    - vars/all.yml

tasks

role/create-aws-cf-distribution/tasks/main.yml
- name: Create Distribution
  shell: |
    aws cloudfront create-distribution \
    --cli-input-json file://files/{{ ENV }}/cf/distribution.json
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always

終わりに

これで、S3バケットをCloudFront経由で表示することが可能になります。
キャッシュを有効利用出来るので、 表示スピードが上がりストレスフリー なページを構築出来ます。

設定用のJsonが少し複雑なので、本記事のJsonをベースに調べながらカスタマイズしてもらえればなと思います。

ただ、このままだとCloudFrontのTTLが効いているのでオブジェクトを更新しても 即時反映 がされません。
また、URLについても、 xxxx.cloudfront.net のようなドメインになっています。

前者については、AWS LambdaによってオブジェクトがPutされたことをトリガーにInvalidationをかけることで解決可能です。(毎回手動でやるのは 運用コスト がかかるので自動化をお勧めします)

後者については、Route53を設定することで独自ドメインによるアクセスが可能になります。

上記の作業についても、aws-cliからの動作確認は済んでいるので時間があるときに記事にまとめようと思っています♪

じゃあの。

続きを読む

Running Kubernetes Cluster on AWS

Following this guide https://kubernetes.io/docs/getting-started-guides/kops/, set up Kubernetes cluster on AWS using kops.

1. Install kops

According to the guide, kops is:

kops helps you create, destroy, upgrade and maintain production-grade, hig… 続きを読む

Rails5アプリケーションのAWSによるネットワーク構築 Nginx+Puma+Capistranoな環境とAWS構築(VPC EC2 RDS CloudFlont Route53 etc)

はじめに

数ヶ月前に書いたRails5のAWS構築をもう少し項目を増やしてから公開しようと思っていたのですが、なかなか出来ないのでもう公開します。(ELBとかCloudFlontとかもっとちゃんと書けそう、、)
項目の追加や修正は適宜入れると思います。

Railsは5.0を使っています。(記事を書いたときの最新)

4系は以前書きました↓

構築する環境

  • VPC
  • EC2
    • Nginx + Puma
    • Capistrano
  • (S3)
  • ELB
  • RDS
  • CloudFlont
  • Route53
  • (CloudWatch)

VPC、EC2のインスタンスの作成

AWSのデザインが多少変更はありますが、以下を参考に作成出来ます。

AWS VPCによるネットワーク構築とEC2によるサーバー構築

これに追加でElastic IPの設定もしておきました。

EC2内の環境構築

作成したEC2にログインして環境を構築します。
ec2-userでまずログインしましょう。

ユーザーの作成

デフォルトではec2-userなので新たなユーザーを追加し、そのユーザーでsshログイン出来るように設定します。

$ sudo adduser shizuma #ユーザーの追加 「shizuma」の部分は好きなユーザー名にします。以後「shizuma」の部分は各自のユーザー名になります。
$ sudo passwd shizuma
#ここで、新規ユーザーのパスワードを設定します。
$ sudo visudo
-----------------------------
#vimが起動するので新規ユーザーにroot権限を与える。
root    ALL=(ALL)       ALL
shizuma ALL=(ALL)       ALL #この行を追加
-----------------------------
$ sudo su - shizuma #ユーザー切り替え
#先ほど設定したパスワード

ここでローカルに一旦戻り、鍵を作成します。

$ cd .ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():first_aws_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ vi config
-----------------------------
# 以下を追記
Host first_aws
  Hostname 54.64.22.197 #自分の設定に合わせて
  Port 22
  User shizuma #先ほどのユーザー名
  IdentityFile ~/.ssh/first_aws_rsa #秘密鍵の設定
-----------------------------

次に、サーバーに戻り作成した 公開鍵 をサーバーに設定します。

$ mkdir .ssh
$ chmod 700 .ssh
$ cd .ssh
$ vi authorized_keys
-----------------------------
#localの「first_aws_rsa.pub」の中身のコピペ。(localで $ cat first_aws_rsa.pubとかすると良い)
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@shizuma-no-MacBook-Air.local
-----------------------------
$ chmod 600 authorized_keys
$ exit
$ exit

これで設定が完了したので、以降作成したユーザーでアクセスするようにします。

基本ライブラリとrubyの環境構築

$ sudo yum install 
git make gcc-c++ patch 
openssl-devel 
libyaml-devel libffi-devel libicu-devel 
libxml2 libxslt libxml2-devel libxslt-devel 
zlib-devel readline-devel 
mysql mysql-server mysql-devel 
ImageMagick ImageMagick-devel 
epel-release
$ sudo yum install nodejs npm --enablerepo=epel
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source .bash_profile
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv rehash
$ rbenv install -v 2.3.1
$ rbenv global 2.3.1
$ rbenv rehash
$ ruby -v

gitの設定

最低限の設定だけしておきます。

$ vi .gitconfig
.gitignore
[user]
  name = your_name #自分の名前
  email = hoge@hoge.com #自分のメアド

[alias] #これはお好きに
  a = add
  b = branch
  ch = checkout
  st = status

[color] #色付け
  ui = true

[url "github:"] #pull、pushのための設定
    InsteadOf = https://github.com/
    InsteadOf = git@github.com:

アプリケーションフォルダの設置

/var/www/rails にアプリケーションフォルダを設置します。

$ cd /
$ sudo mkdir -p /var/www/rails
$ sudo chown -R shizuma var/www

GitHubの接続とアプリケーションのclone

$ cd ~/.ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():aws_git_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ chmod 744 config
$ vi config
-----------------------------
# 以下を追記
Host github github.com
  Hostname github.com
  User git
  Port 22
  IdentityFile ~/.ssh/aws_git_rsa #秘密鍵の設定
-----------------------------
$ cat aws_git_rsa.pub
-----------------------------
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@ip-10-0-1-10
-----------------------------

ここで、これをコピペしてgithubに公開鍵を登録する。
githubへの鍵の登録がよくわからない方は以下の記事を参考に。
gitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

そして、git clone する。

$ cd /var/www/rails
$ git clone https://github.com/kuboshizuma/cheerfull # 自分のアプリケーション

RDSの設定

EC2の環境構築に一区切りついたのでRDSの設定を行います。

DBサブネットの登録

RDSで使うサブネットを登録します。
この設定には異なるアベイラビリティゾーンにあるサブネットが最低1つずつ計2つ必要になります。
また、Railsアプリケーションをおいたサブネットはゲートウェイに繋がったpublicなサブネットなので、privateなサブネットを異なるアベイラビリティゾーンに一つずつ作成します。

スクリーンショット_2017-03-04_11_56_01.png

パラメータグループの設定

パラメータグループを設定します。
mysqlを使用していると設定なので、ここではmysqlを選択します。バージョンは適宜選択。
「chara…」で検索すると出て来るcharasetをutf-8に変更。

スクリーンショット_2017-03-04_12_00_57.png

設定が完了した後に、パラメータの編集を押してパラメーター変更。

スクリーンショット_2017-03-04_12_04_29.png

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

VPCでセキュリティグループを作成します。

スクリーンショット_2017-03-04_12_08_19.png

インバウンドルールにMySQLを設定し、MySQLのアクセスのみ許可します。
「送信元」は0.0.0.0/0で設定。特定のRailsアプリケーションサーバーがあるサブネットに限定するほうがよさそう。

スクリーンショット 2017-03-04 12.55.42.png

RDSインスタンスの作成

エンジンの選択

スクリーンショット_2017-03-04_12_12_05.png

本番稼働用?

スクリーンショット_2017-03-04_12_12_45.png

DB詳細設定の指定

無料枠はt2.micro

スクリーンショット_2017-03-04_12_17_35.png

詳細設定の設定

各種作成したもので設定。

スクリーンショット_2017-03-04_12_19_44.png

スクリーンショット_2017-03-04_12_19_53.png

接続確認

hostは各自の作成したRDSインスタンスのエンドポイントをみる。

$ mysql -h hogepoge.ap-northeast-1.rds.amazonaws.com -u shizuma -P 3306 -p

接続出来たら完了!

絵文字の扱い

絵文字も登録出来るようにする場合。
以下を参考に utfmb4を採用する。

ActiveRecordをutf8mb4で動かす

MAMPでは /Applications/MAMP/conf/my.cnfmy.cnf を設置した。

Railsアプリケーションの起動のための準備

puma setting

以下を追記する。

cofig/puma.rb
# add setting for production
_proj_path = "#{File.expand_path("../..", __FILE__)}"
_proj_name = File.basename(_proj_path)
_home = ENV.fetch("HOME") { "/home/#{ENV['PUMA_USER']}" }
pidfile "#{_home}/run/#{_proj_name}.pid"
bind "unix://#{_home}/run/#{_proj_name}.sock"
directory _proj_path
# add end

ENV['PUMA_USER'] にサーバーのユーザー名が入るように環境変数を設定。

database setting

以下のように編集。

config/database.yml
production:
  <<: *default
  database: cheerfull
  username: <%= ENV['DATABASE_USER_NAME_PRODUCTION'] %>
  password: <%= ENV['DATABASE_PASSWORD_PRODUCTION'] %>
  host: <%= ENV['DATABASE_HOST_PRODUCTION'] %>

それぞれ該当の環境変数を設定。

rake secret

以下のsecret keyの箇所に値が入るように環境変数を設定。
rake secret とコマンドを打つと出て来る文字列を設定する。

config/secrets.yml
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
$ gem install bundler
$ bundle install
$ rake db:create RAILS_ENV=production
$ rake db:migrate RAILS_ENV=production

PumaとNginxの起動

Pumaの起動

アプリケーションディレクトリにて以下を実行。

$ RAILS_SERVE_STATIC_FILES=true RAILS_ENV=production puma -w 4

「Ctr+c」でプロセス消さなければ生き残る。
プロセス消す必要があれば以下のようにする。

$ ps aux | grep puma # プロセスIDを探す
$ kill -9 (ID)

Nginx の起動

「cheerfull」の部分は自分のアプリケーションディレクトリ名に変更する。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/log/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/home/shizuma/run/cheerfull.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }

ユーザーをnginxから自分のユーザー名に変更しておく。

/etc/nginx/nginx.conf
#user nginx;
user shizuma;

nginxを起動します。

$ sudo service nginx restart

これで、IPアドレスでアクセスするとアプリケーションが表示されるようになりました。

Capistranoの設定

諸々動くことが確認出来たのでデプロイが出来るように設定します。
デプロイのためにCapistranoの設定をします。
慣れてきたら、いきなりCapistranoの設定をしていけばいいと思います。

socketの場所もアプリケーションディレクトリに変更するのでnginxの設定もそれに合わせて変更します。

「cheerfull」の部分は自分のアプリケーション名に変更する。

deploy.rb
require 'dotenv'
Dotenv.load

lock "3.7.1"

set :application, "cheerfull"
set :repo_url, "git@github.com:your_gitname/cheerfull.git"
set :branch, 'master'
set :deploy_to, '/var/www/rails/protospacce'
set :linked_files, fetch(:linked_files, []).push('.env')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
set :keep_releases, 5
set :rbenv_ruby, '2.3.1'

# puma setting
set :puma_threads,    [4, 16]
set :puma_workers,    0
set :pty,             true
set :use_sudo,        false
set :stage,           :production
set :deploy_via,      :remote_cache
set :deploy_to,       "/var/www/rails/#{fetch(:application)}"
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state,      "#{shared_path}/tmp/pids/puma.state"
set :puma_pid,        "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{shared_path}/log/puma.error.log"
set :puma_error_log,  "#{shared_path}/log/puma.access.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true

namespace :deploy do
  desc 'Create database'
  task :db_create do
    on roles(:db) do |host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end
end

実際にdeployする前に、共有ファイルは用意しておきましょう。この場合、.env

ここでpumaのセッティングをしたので、config/puma.rb はもう不要になります。
また、dotenvを使用する場合は変更を読み込むために config/deploy/templates/puma.rb.erb を用意します。以下を用意。

config/deploy/templates/puma.rb.erb
#!/usr/bin/env puma

directory '<%= current_path %>'
rackup "<%=fetch(:puma_rackup)%>"
environment '<%= fetch(:puma_env) %>'
<% if fetch(:puma_tag) %>
  tag '<%= fetch(:puma_tag)%>'
<% end %>
pidfile "<%=fetch(:puma_pid)%>"
state_path "<%=fetch(:puma_state)%>"
stdout_redirect '<%=fetch(:puma_access_log)%>', '<%=fetch(:puma_error_log)%>', true


threads <%=fetch(:puma_threads).join(',')%>

<%= puma_bind %>
<% if fetch(:puma_control_app) %>
activate_control_app "<%= fetch(:puma_default_control_app) %>"
<% end %>
workers <%= puma_workers %>
<% if fetch(:puma_worker_timeout) %>
worker_timeout <%= fetch(:puma_worker_timeout).to_i %>
<% end %>

<% if puma_preload_app? %>
preload_app!
<% else %>
prune_bundler
<% end %>

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = "<%= fetch(:bundle_gemfile, "#{current_path}/Gemfile") %>"
  ENV.update Dotenv::Environment.new('.env')
end

<% if puma_preload_app? and fetch(:puma_init_active_record) %>
before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end
<% end %>
$ bundle exec production cap puma:config 

これでこの設定を本番に反映。

あとはデプロイコマンドを打つ。

$ bundle exec cap production deploy

デプロイdone!

nginxのlogの位置やsocketの位置を変更します。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/shared/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/shared/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/var/www/rails/cheerfull/shared/tmp/sockets/cheerfull-puma.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/current/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/current/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }
$ sudo service nginx restart

これで、デプロイ環境が整いました。
以前起動したpumaのプロセスはきっておきましょう。

複数サーバーへのデプロイ

multiconfig を使用します。

Gemfile
gem 'capistrano-multiconfig', '~> 3.1', require: false

Capfileのcapistrano/setupから変更。

Capfile
# require "capistrano/setup"
require 'capistrano/multiconfig'

config/deploy/production.rb から config/deploy/app1/production.rb に変更する。
以下のようにdeploy出来るようになる。

$ bundle exec cap app1:production deploy

違うサーバーにデプロイするときは同じように config/deploy/app2/production.rb を準備すればオーケー。

$ bundle exec cap app2:production deploy

これで違うサーバーにデプロイ出来る。

これまでと同様にec2サーバーをもう一度用意して2箇所にデプロイ出来るようにする。

ロードバランサー ELB

ロードバランサーをhttpsで運用したいときadmin@hoge.comのようなアドレスへのメールを受信可能のしないといけない。

以下でとりあえずzoneファイルのインポートまでする。
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)

その後にこの記事にあるようにお名前.com側の設定をする。
お名前.com + Route53で独自ドメインのメールをGmailに転送する

メールの転送が出来ていることを確認する。
そして、ELBの指示に従って設定。
下記参考。
【初心者向け】ELBにSSL証明書をインストールする

そこまでしたら
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)
の続きでELBとつなげる。

注意点は、ターゲットグループにトラフィックをルーティングするときのプロトコルをHTTPにすること。これでELBへはHTTPSでアクセス出来、それをアプリケーションではHTTPで扱うことが出来る。

また、以下のように nginxのサーバーネームにドメイン名を追加する必要がある。

server_name hogepoge.com 00.000.00.00 sakamichi-app-elb-000000000.ap-northeast-1.elb.amazonaws.com

CloudFront

Amazon CloudFront + ACM 独自ドメインで HTTPS (SSL) 配信設定メモ

CloudWatchとか

必要の応じて加筆。

続きを読む

トラフィックフローを試してみた

TL;DR

Amazon Route53 のトラフィックフローは、複雑なルーティングポリシーを実現するための魅力的なソリューションです。ただし、カンニングはできなさそうです(後述)。

目的

オンプレに2台のWebサーバがあり、それぞれ個別のグローバルIPを持っています。これら2台ともコケた場合の BCP 対策として、S3+CloudFront で構築した Sorry ページを表示するというのを Route53 を使って実現することを考えます。なお、HTTPS 対応の構成図を掲載しますが、HTTPS 自体は本質的ではないので説明は省略します。

overview.png

本来の構成は、インターネットから見て Web サーバー群の前段にロードバランサーがあって、そいつが回線負荷を考慮してこれらのいずれか一方 の A Record を返す(ように DNS Responseを書き換える)ということをやっているらしいです。しかしながら、Route53 からは回線負荷の状況などわかるはずもないので、A Record として両方の IP をなるべく公平に返してやることにします。

考え方

2台のWebサーバーを合わせた1個の仮想的なサービスを現用系、Sorry ページを待機系とするフェイルオーバー構成として構築します。まず、テスト用に立てた2台のサーバ fo-test1, fo-test2 に対して、それぞれ HTTP でヘルスチェックを行います。さらに、『これらいずれかが稼働中』を表す仮想的なヘルスチェック either-ok を作りました。ちなみにヘルスチェックの価格は、AWS 以外のエンドポイントに対して1件あたり0.75$/月です。HTTPS監視とか監視間隔を短くしたいといった向きには、多少オプション価格が上乗せされます(Amazon Route53 料金)。

health-check.png

複数の IP アドレスを公平に返すのは、ルーティングポリシーの Multivalue Answer で実現可能です。これらを組み合わせて、以下のようにすることにします。

Case fo-test1 fo-test2 either-ok A Record
1 Healthy Healthy Healthy fo-test1, fo-test2
2 Healthy Unhealthy Healthy fo-test1
3 Unhealthy Healthy Healthy fo-test2
4 Unhealthy Unhealthy Unhealthy xxx.cloudfront.net

ところが、『これらいずれかが稼働中』をフェイルオーバーの現用系とするための設定方法が、どうしてもわかりませんでした。仕方がないので、最後の手段としてトラフィックフローというのを試してみました。これは、複雑なルーティングをビジュアルエディタで構成でき、ルールのバージョン管理やロールバックも自由にできるという優れものです。

なぜ最初からこれでやらなかったかというと、これがちょっとお高いからです(ポリシーあたり50$/月)。

結果的には、めちゃくちゃ簡単。2分で設定完了。

traffic-flow.png

カンニング失敗

実は、トラフィックフローとかいいながら、実は単にビジュアルエディタで設定した内容を通常のリソースレコードに展開するんだろう。だからそれを書き留めて、トラフィックフローを解除してそれらのルールを手で追加すれば料金かからないはず。俺って天才じゃね?、的なことを当初考えていました。

ところが、トラフィックフローの機能としてはちゃんと動いているのに、リソースレコードの一覧には “Traffic policy record” の1件しか表示されません。どうも、トラフィックフローはリソースレコードとは完全に別管理になっており、その詳細ルールはリソースレコードとしては見えないようです。使いたかったらちゃんと金払えってことですね。

resourece-list.png

続きを読む

AWS上にBINDでDNSサーバ構築(キャッシュサーバ、権威サーバ)

Amazon Linux上にBINDでキャッシュサーバと権威サーバのそれぞれのシンプルな動作手順をメモします。キャッシュサーバ、権威サーバのそれぞれのサーバについて、最初に入力するコマンドの流れを書いて、後に各設定ファイルの内容を書いていきます。セキュリティ等について各自責任で注意をお願いします。

キャッシュサーバ 構築

$ sudo su
# yum install -y bind
# vim /etc/named.conf
# named-checkconf /etc/named.conf
# vim /etc/sysconfig/network-scripts/ifcfg-eth0
# vim /etc/resolv.conf
# service named start

named.conf

listen-on port 53 { 127.0.0.1; }; から listen-on port 53 { 127.0.0.1; any; }; へ変更
allow-query { localhost; }; から allow-query { localhost; any; }; へ変更
allow-query-cache { localhost; any; }; を追加

named.conf
//
// named.conf
//
// Provided by Red Hat bind package to configure the ISC BIND named(8) DNS
// server as a caching only nameserver (as a localhost DNS resolver only).
//
// See /usr/share/doc/bind*/sample/ for example named configuration files.
//

options {
        listen-on port 53 { 127.0.0.1; any; };
        listen-on-v6 port 53 { ::1; };
        directory       "/var/named";
        dump-file       "/var/named/data/cache_dump.db";
        statistics-file "/var/named/data/named_stats.txt";
        memstatistics-file "/var/named/data/named_mem_stats.txt";
        allow-query     { localhost; any; };
        allow-query-cache     { localhost; any; };
        recursion yes;

        dnssec-enable yes;
        dnssec-validation yes;

        /* Path to ISC DLV key */
        bindkeys-file "/etc/named.iscdlv.key";

        managed-keys-directory "/var/named/dynamic";
};

logging {
        channel default_debug {
                file "data/named.run";
                severity dynamic;
        };
};

zone "." IN {
        type hint;
        file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

# service bind start
# chkconfig --list named

ifcfg-eth0

DNS=172.31.47.1を追加(プライベートIP)

DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes
TYPE=Ethernet
USERCTL=yes
PEERDNS=yes
DHCPV6C=yes
DHCPV6C_OPTIONS=-nw
PERSISTENT_DHCLIENT=yes
RES_OPTIONS="timeout:2 attempts:5"
DHCP_ARP_CHECK=no
DNS=172.31.47.1

resolve.conf

nameserver 172.31.47.1のようにネームサーバのIPをAmazon Linux起動時に割り振られたプライベートIPに変更する。

resolv.conf
; generated by /sbin/dhclient-script
search us-west-2.compute.internal
options timeout:2 attempts:5
nameserver 172.31.47.1

ローカルのDNSキャッシュの削除して検証

$ dscacheutil -flushcache
$ dig @35.165.154.140 google.com

; <<>> DiG 9.8.3-P1 <<>> @35.165.154.140 google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51142
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 4

;; QUESTION SECTION:
;google.com.            IN  A

;; ANSWER SECTION:
google.com.     274 IN  A   216.58.193.78

;; AUTHORITY SECTION:
google.com.     172774  IN  NS  ns1.google.com.
google.com.     172774  IN  NS  ns3.google.com.
google.com.     172774  IN  NS  ns2.google.com.
google.com.     172774  IN  NS  ns4.google.com.

;; ADDITIONAL SECTION:
ns2.google.com.     172774  IN  A   216.239.34.10
ns1.google.com.     172774  IN  A   216.239.32.10
ns3.google.com.     172774  IN  A   216.239.36.10
ns4.google.com.     172774  IN  A   216.239.38.10

;; Query time: 151 msec
;; SERVER: 35.165.154.140#53(35.165.154.140)
;; WHEN: Sun Aug  6 19:55:38 2017
;; MSG SIZE  rcvd: 180

権威サーバ 構築

ドメイン名はRoute53で登録したものを使用しています。

$ sudo su
# yum install -y bind
# vim named.conf
# rndc-confgen -a
# named-checkconf
# named-checkzone dns.hayashier.com dns.hayashier.com.zone
# vim /etc/sysconfig/named
# service named start

named.conf

pid-file “/var/run/named/named.pid”;
allow-transfer { none; };
を追加。
recursion yes; から recursion no;へ変更

named.conf
// Provided by Red Hat bind package to configure the ISC BIND named(8) DNS
// server as a caching only nameserver (as a localhost DNS resolver only).
//
// See /usr/share/doc/bind*/sample/ for example named configuration files.
//

options {
        listen-on port 53 { 127.0.0.1; };
        listen-on-v6 port 53 { ::1; };
        directory       "/var/named";
        dump-file       "/var/named/data/cache_dump.db";
        statistics-file "/var/named/data/named_stats.txt";
        memstatistics-file "/var/named/data/named_mem_stats.txt";
        allow-query     { localhost; };
        recursion no;

        dnssec-enable yes;
        dnssec-validation yes;

        /* Path to ISC DLV key */
        bindkeys-file "/etc/named.iscdlv.key";

        managed-keys-directory "/var/named/dynamic";

        pid-file "/var/run/named/named.pid";
        allow-transfer { none; };
};

logging {
        channel default_debug {
                file "data/named.run";
                severity dynamic;
        };
};

zone "." IN {
        type hint;
        file "named.ca";
};

zone "dns.hayashier.com" {
        type master;
        file "/etc/dns.hayashier.com.zone";
};

include "/etc/rndc.key";

controls {
      inet 127.0.0.1 port 953
      allow { 127.0.0.1; } keys { "rndc-key"; };
};


include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

dns.hayashier.com.zone

dns.hayashier.com.zone
$ORIGIN dns.hayashier.com.
$TTL 900
@      IN          SOA   ns.dns.hayashier.com. sample.dns.hayashier.com. (
                        2017080701         ; Serial
                        3600               ; Refresh
                        900                ; Retry
                        1814400            ; Expire
                        900 )              ; Minimum
       IN          NS   ns-1092.awsdns-08.org.
       IN          A    52.43.178.10

named

OPTIONS=”-4″を追加

# BIND named process options
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
# Currently, you can use the following options:
#
# ROOTDIR="/var/named/chroot"  --  will run named in a chroot environment.
#                            you must set up the chroot environment
#                            (install the bind-chroot package) before
#                            doing this.
#       NOTE:
#         Those directories are automatically mounted to chroot if they are
#         empty in the ROOTDIR directory. It will simplify maintenance of your
#         chroot environment.
#          - /var/named
#          - /etc/pki/dnssec-keys
#          - /etc/named
#          - /usr/lib64/bind or /usr/lib/bind (architecture dependent)
#
#         Those files are mounted as well if target file doesn't exist in
#         chroot.
#          - /etc/named.conf
#          - /etc/rndc.conf
#          - /etc/rndc.key
#          - /etc/named.rfc1912.zones
#          - /etc/named.dnssec.keys
#          - /etc/named.iscdlv.key
#
#       Don't forget to add "$AddUnixListenSocket /var/named/chroot/dev/log"
#       line to your /etc/rsyslog.conf file. Otherwise your logging becomes
#       broken when rsyslogd daemon is restarted (due update, for example).
#
# OPTIONS="whatever"     --  These additional options will be passed to named
#                            at startup. Don't add -t here, use ROOTDIR instead.
#
# KEYTAB_FILE="/dir/file"    --  Specify named service keytab file (for GSS-TSIG)
#
# DISABLE_ZONE_CHECKING  -- By default, initscript calls named-checkzone
#                           utility for every zone to ensure all zones are
#                           valid before named starts. If you set this option
#                           to 'yes' then initscript doesn't perform those
#                           checks.

OPTIONS="-4"

検証

$ dig @localhost dns.hayashier.com. ANY

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.62.rc1.56.amzn1 <<>> @localhost dns.hayashier.com. ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37038
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;dns.hayashier.com.     IN  ANY

;; ANSWER SECTION:
dns.hayashier.com.  900 IN  SOA ns.dns.hayashier.com. sample.dns.hayashier.com. 2017080702 3600 900 1814400 900
dns.hayashier.com.  900 IN  NS  ns-1092.awsdns-08.org.
dns.hayashier.com.  900 IN  A   52.43.178.10

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun Aug  6 15:12:53 2017
;; MSG SIZE  rcvd: 132

参考資料

https://www.tecmint.com/install-configure-cache-only-dns-server-in-rhel-centos-7/
http://www.atmarkit.co.jp/ait/articles/1502/10/news010.html

続きを読む

Route53で負荷分散をするための選択肢が増えた話

TL;DR

Route53 を使っていて、たとえば A Record に対して複数の IP アドレスをなるべく平等に返したい場合(ただしサービスが止まっているノードは除外する)の選択肢として Routing Policy=Weighted(加重リソースレコードセット)がありますが、平等にするにはすべてに同じ重み(weight)を指定する必要があります。

ところが最近(?)Routing Policy=Multivalue Answer(複数値回答)という選択肢が増えたようで、これで毎回異なった値を平等に返してくれるようです。重みの指定は不要です(指定不可)。もちろん Routing Policy=Weighted と同様に、ヘルスチェックを関連付けておけば、ヘルスチェックで Unhealthy なノードは自動的に対象から除外してくれます。

はじめに

フェイルオーバーのしくみを調べていたら、Routing Policy=Multivalue Answer という選択肢が出ることに気づいたのですが、これは日本語ドキュメントには載っていません。原文には載っていたので和訳が追いついていないのだと思いますが、Route53開発者ガイドは現時点でも PDF で 420 ページという大作ですので、AWS の中の人はかなりがんばっているという印象です。

以下に、簡単に選択肢を紹介します。

レコードセットの設定

項目名 値/選択肢
NAME 当該ドメインのサブドメイン(ホスト名)部分
TYPE (たとえば)A – IPv4 Address
Alias(Route53 固有) 【レコードタイプが SOA/NS 以外の場合に選択可能】
Yes:アクセス先が AWS が動的に割り当てる FQDN の場合、
  または別のリソースレコード(同一ホストゾーン内)
No :直に IP アドレスを書く場合

Alias=Yes の場合

項目名 値/選択肢
Alias Target AWS 管理の FQDN(IP アドレスが動的に変わる)等
Routing Policy Simple:常に同じ値を返す(デフォルト)
Weighted:加重による分散
Latency:レイテンシによる分散
Failover:アクティブ/スタンバイ方式による分散
Geolocation:アクセス元の国や地域による分散
Routing Policy
=Weighted
Weight:加重値
Set ID:加重セットグループ中でユニークな文字列
Routing Policy
=Latency
Region:対象リージョン
Set ID:ユニーク文字列
Routing Policy
=Failover
Failover Record Type:Primary または Secondary
Set ID:ユニーク文字列
Routing Policy
=Geolocation
Location:国名や地域を選択肢から選択
Set ID:ユニーク文字列
Evaluate Target Health Yes:AWS 管理の FQDNのヘルスチェックと連携する
  (ただし Alias 先が CloudFront の場合は選択不可)
No :ヘルスチェックを考慮しない
Associate with Health Check 【Routing Policy が Simple の場合は指定不可】
Yes:何らかのヘルスチェックに関連付ける。
  ヘルスチェックが正常でない場合、返す値からそのリソースを除外する。
  これを選択する場合、TTLは60以下を推奨。
No :返す値の絞り込みを行わない。
Associate with Health Check
= Yes
Health Check to Associate:関連付ける対象のヘルスチェック(選択肢から選択)

Alias=No の場合

項目名 値/選択肢
TTL(秒数) クライアントがレコードをキャッシュ可能な時間
Value IPアドレスの値(複数可の場合もある)
Routing Policy Weighted / Latency / Failover / Geolocation(これらは Alias=Yes の場合と同じ扱い)
Routing Policy
=Multivalue Answer
Set ID:ユニーク文字列
最大8個の中から正常なレコードを抽出し、複数の値を毎回ランダムな順序で返す。
Routing Policy=Simple で同じ名前を登録しようとすると二重登録エラーとなるが、Routing Policy = Multivalue Answer でかつ Set ID を異なった値にすることで、同じ名前で複数レコードを登録できる。

おまけ

これを調べている間に日本語マニュアルの中にいくつか不備をみつけたので、各ページ最下段にある [フィードバック] から一応報告しましたが、果たして拾ってもらえるかどうか…。

続きを読む

cloud frontとlambda@edgeでUA判定をしてPCページとSPページを出し分けてみた

cloud frontとlambda@edgeでUA判定をしてPCページとSPページを出し分けてみた

Webサービスを構築する仕事がきたのですが、ビジネスロジックが不要なサービスだったので
できるだけカンタンに作ろうと思いサーバーレス構築に挑戦してみた際にやってみたレポートになります。

使ったAWSのサービス

  • cloud front
  • lambda
  • S3
  • Route53(ドメイン設定の話なので今回は特に登場させません)
  • ACM(SSL証明書の話なので今回は特に登場させません)
  • Cloud Watch(lambdaを利用したら自動でついてきた)

やったこと

  1. S3にHTMLを配置
  2. cloud frontとS3を紐付け
  3. ドメインにアクセスした際にどのHTMLを出すか設定(トップページの設定)
  4. lambda@edgeにUA判定処理を実装
  5. lambda@edgeとcloud frontの紐付け(トリガー設定)今回はViewerリクエストをトリガーに設定

ざっくり動くまででだいたい2〜3時間くらいでできました。

1〜3はいろいろなところに記事が落ちているのでlambdaのソースと引っかかったポイントを紹介します。

lambdaに実装したソース

'use strict';

const whitelist = [
  'android',
  'iphone',
  'ipad'
];

const isSpBrowser = (uas) => {
  if (uas && Array.isArray(uas) && uas.length > 0) {
    return uas.some(ua => whitelist.some(w => ua.value.toLowerCase().indexOf(w) !== -1));
  }
  return false;
};

// User-AgentからPC・SP判定を行ない描画ページを切り替える
exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // console.log('headers.user-agent. = ' + headers['user-agent']);
    if (headers['user-agent']) {
        // URI整理
        // cloud frontにトップページとして設定したindex.htmlがURIに入ってくるので除去
        var uri = request.uri.slice(-10) === ('index.html') ? request.uri.slice(0, -10) : request.uri;
        uri = uri.slice(-1) === ('/') ? uri : uri + '/';
        console.log('headers[user-agent] = ' + headers['user-agent'][0].value);
        // URI判定
        // ファイル読み込みで呼び出された場合中断
        if(uri.match(/(png|css|js|jpeg|jpg|bmp|gif|pdf|svg|zip)/)) {
            callback(null, request);
            return;
        }
        // UA判定
        if (isSpBrowser(headers['user-agent'])) {
            uri = uri + 'SP用描画ファイル名'
        } else {
            uri = uri + 'PC用描画ファイル名'
        }
        // XXX 現状パラメータを渡すことができない
        // uri = param === '' ? uri : uri + '?' + param
        console.log('uri = ' + uri);
        request.uri = uri
        callback(null, request);
        return;
    }
    callback(null, request);
};

ポイント

  1. ログはCloud Watchに出力されるがアクセスされたcloud frontのリージョンに出力されたのでlambdaを配置しているリージョンではない
  2. クエリストリングは使えない(2017年8月現在)
  3. ドキュメントは基本英語版が正
  4. トリガー設定はlambda側からやるほうがらく
  5. 困ったら迷わず有料サポートを使う(結果そのほうが早いのでトータルでの費用が下がる)

最後に

イベントページなどの後ろにビジネスロジックがいらないサービスであればこの構成は結構おすすめできると思います。
インフラ作業部分(エンジニアが必要な作業分)は大体5人日程度で収まったのでその分デザインやプロモーションにコストがまわせるのも強みかと
※初回なのでこれくらいですが慣れればもっと短くなる予感

続きを読む

[localstack] ローカル環境にAWSサービスのモックを作り開発をする

AtlassianのLocalStackを使ってみてなんとなく理解するまでのお話 の通りなのですが本家のgithubを見るとこんな感じになってたので現行どうするかを備忘録としてメモします。

 
 スクリーンショット 2017-08-02 16.33.49.png

Atrasian/localstack -> localstack/localstack に変更になってます。

前提

  • Mac(macOS Sierra)
  • Docker Commnity Edition (Version: 7.06.0-ce-mac19)

  • dockerコマンドがコンソール上で実行可能であること

  • AWS CLIの設定が完了していること

イメージの準備

pull
docker pull localstack/localstack

起動

run
docker run -it -p 4567-4582:4567-4582 -p 8080:8080 localstack/localstack

起動させたらコンソールは放置

確認

ブラウザを立ち上げ

http://localhost:8080/

へアクセス

こんな画面が立ち上がってれば安心して大丈夫

エンドポイント

githubから転記してきましたが、変わってる可能性があるので必ず本家で確認して下さい。
https://github.com/localstack/localstack

サービス名 エンドポイントURL
API Gateway http://localhost:4567
Kinesis http://localhost:4568
DynamoDB http://localhost:4569
DynamoDB Streams http://localhost:4570
Elasticsearch http://localhost:4571
S3 http://localhost:4572
Firehose http://localhost:4573
Lambda http://localhost:4574
SNS http://localhost:4575
SQS http://localhost:4576
Redshift http://localhost:4577
ES (Elasticsearch Service) http://localhost:4578
SES http://localhost:4579
Route53 http://localhost:4580
CloudFormation http://localhost:4581
CloudWatch http://localhost:4582

AWS CLI

CLIコマンドを発行する場合
--endpoint-url で上記のエンドポイントを指定して使用します。
デフォルトのリージョンをconfigで指定していない場合、コマンドで--region を指定する必要があります。

サンプル(SQS)
aws sqs create-queue 
        --queue-name 'SAMPLE' 
        --region ap-northeast-1 
        --endpoint-url http://localhost:4576 
        --profile $myProfile

SDK(nodejs)

サンプル
'use strict';

const AWS = require('aws-sdk');
const endPoint = new AWS.Endpoint('http://localhost:4578');
const region = 'ap-northeast-1';
const s3 = new AWS.S3({ endpoint: endPoint, region: region });

基本的にはエンドポイントに上記URLを指定するだけでAPIをリクエストすることが出来ます。
ElasticSerchはすでに使用可能な状態のElasticSerchが動いています。ESの方はドメイン作成とかのAPI用のモックって感じですね。
開発の際に特にデータ系は何かレスポンスが必要なので地味に使えるツールですね

続きを読む