シンプルなRails環境を最速プロビジョニング。各種ツールの利用比較 (Chef [Berkshelf], Ansible [Playbook], Docker [Docker Compose], 手動)

プロビジョニングのための構成管理フレームワークには様々なものがあり、例に挙げると以下のようなものがあります。

  • Chef
  • Ansible
  • Puppet
  • SaltStack
  • CFEngine
  • PowerShell DSC
  • Itamae
  • AWS CloudFormation
  • Terraform
  • Mobingi

ItamaeはCookpadの社員の方が開発した、機能がシンプルで学習コストが低く、Chefを簡略化したようなものです。AWS CloudFormationはAWS上のサーバなどの構成をJSONまたはYAMLで記述して管理するものです。TerraformVagrantPackerなどで有名なHashiCorp社により開発されたもので、AWSやGCP、Azureなどの様々なクラウドに対応した管理ツールとなっています。Mobingiは、従来のようなChefやAnsibleは開発者をターゲットとした扱いの難しいものとして、クラウドのデスクトップとしてGUIベースで管理できるというものです。

ここでは、Chef,Ansible,Dockerによる設定例を取り上げます。
ChefはBerkshelf、AnsibleはAnsible Playbook、DockerはDocker Composeを用いました。また、手動による設定のインストールの例も取り上げました。

例ではそれぞれ最後に、ChefはAWS OpsWorks、AnsibleはAmazon EC2、DockerはAmazon ECSを例に行っていますが、他の環境の場合は適宜置き換えて対応してください。

Chef [Berkshelf]

ローカルでテストせずにOpsWorksでデプロイする場合はVagrant周りの設定は不要で、サブタイトルに(※)のマークを入れた

  • Gemfileの作成
  • Berksfileの作成
  • Recipeの作成

の3つをやれば良い。

ディレクトリ構成

.
├── Berksfile
├── Gemfile
├── README.md
├── Vagrantfile
└── site-cookbooks
    ├── nginx
    │   ├── CHANGELOG.md
    │   ├── README.md
    │   ├── attributes
    │   │   └── default.rb
    │   ├── metadata.rb
    │   ├── recipes
    │   │   └── default.rb
    │   └── templates
    │       └── default
    │           └── nginx.conf.erb
    ├── nodejs
    │   ├── CHANGELOG.md
    │   ├── README.md
    │   ├── attributes
    │   │   └── default.rb
    │   ├── metadata.rb
    │   └── recipes
    │       └── default.rb
    └── ruby-env
        ├── CHANGELOG.md
        ├── README.md
        ├── attributes
        │   └── default.rb
        ├── metadata.rb
        ├── recipes
        │   └── default.rb
        └── templates
            └── default

VagrantでCentOS 6.7の環境設定

$ vagrant box add centos6-7 https://github.com/CommanderK5/packer-centos-template/releases/download/0.6.7/vagrant-centos-6.7.box
$ mkdir -p mkdir ./projects/chef_zero_test
$ cd projects/chef_zero_test
$ vagrant init centos6.7

Vagrantの設定ファイルを以下のように編集する。

$ vim Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "centos6.7"
  config.vm.box_url = "https://github.com/CommanderK5/packer-centos-template/releases/download/0.6.7/vagrant-centos-6.7.box"
  config.vm.network "private_network", ip: "192.168.33.10"
  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--memory", "4096"]
  end
end

このままでVagrantを立ち上げると、SSH接続の設定で

default: Warning: Authentication failure. Retrying...

のようなエラーが繰り返し表示されて権限の問題でできないので、VagrantにSSHで入って

vagrant ssh

ホスト側で以下のコマンドを実行する。

cd /home
chmod 755 -R ./vagrant
exit

http://qiita.com/jshimazu/items/9db49ce64478e82d511e

Vagrantを立ち上げる。

vagrant up

Vagrantfileの設定

Vagrantfileをプロビジョニングの設定を以下のように追加する。

Vagrant.configure("2") do |config|
  config.vm.box = "centos6.7"
  config.vm.box_url = "https://github.com/CommanderK5/packer-centos-template/releases/download/0.6.7/vagrant-centos-6.7.box"
  config.vm.network "private_network", ip: "192.168.33.10"
  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--memory", "4096"]
  end

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
    chef.json = {
      nginx: {
        env: ["ruby"]
      }
    }
    chef.run_list = %w[
      recipe[yum-epel]
      recipe[nginx]
      recipe[ruby-env]
      recipe[nodejs]
    ]
  end
end

Gemfileの作成(※)

以下のようにGemfileを作成する。

$ vim Gemfile
source 'https://rubygems.org'

gem 'chef'
gem 'knife-solo'
gem 'berkshelf'
$ cd projects/chef_zero_test
$ rbenv exec bundle install

Berksfileの作成(※)

以下のようにBerksfileを作成する。

$ vim Berksfile
source "https://supermarket.chef.io"

cookbook "yum-epel"
cookbook "nginx", path: "./site-cookbooks/nginx"
cookbook "ruby-env", path: "./site-cookbooks/ruby-env"
cookbook "nodejs", path: "./site-cookbooks/nodejs"

※最初、site :opscodeの宣言方法はDeplicated

Recipeの作成(※)

nginxのレシピ

1.Cookbookの作成。

bundle exec knife cookbook create nginx -o ./site-cookbooks

2.Recipeファイルの作成。

vim site-cookbooks/nginx/recipes/default.rb
default.rb
include_recipe "yum-epel"

package "nginx" do
  action :install
end

service "nginx" do
  action [ :enable, :start ]
  supports :status => true, :restart => true, :reload => true
end

template 'nginx.conf' do
  path '/etc/nginx/nginx.conf'
  source "nginx.conf.erb"
  owner 'root'
  group 'root'
  mode '0644'
  notifies :reload, "service[nginx]"
end

3.attributeファイルの作成。

vim site-cookbooks/nginx/attributes/default.rb
default.rb
default['nginx']['env'] = []

4.nginx.confのテンプレートファイルの作成。

sudo cp /usr/local/etc/nginx/
nginx.conf ~/workspace/chef-tests/chef-test/projects/chef_zero_test/site-cookbooks/nginx/templates/default/nginx.conf.erb
vim site-cookbooks/nginx/template/default/nginx.conf.erb
nginx.conf.erb
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    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;
    keepalive_timeout  65;

    upstream app_server {
      server unix:/tmp/unicorn.sock;
    }

    server {
        listen       80 default_server;
        server_name  _;

        location / {
            rewrite ^/app_server/(.+) /$1 break;
            proxy_pass http://app_server/$1;
        }
    }
}

Rubyのレシピ

1.Cookbookの作成。

$ bundle exec knife cookbook create ruby-env -o ./site-cookbooks

2.Recipeファイルの作成。

$ vim site-cookbooks/ruby-env/recipes/default.rb
%w{vim git openssl-devel sqlite-devel readline-devel}.each do |pkg|
    package pkg do
        action :install
    end
end

git "/home/#{node['ruby-env']['user']}/.rbenv" do
    repository node["ruby-env"]["rbenv_url"]
    action :sync
    user node['ruby-env']['user']
    group node['ruby-env']['group']
end

template ".bash_profile" do
    source ".bash_profile.erb"
    path "/home/#{node['ruby-env']['user']}/.bash_profile"
    mode 0644
    owner node['ruby-env']['user']
    group node['ruby-env']['group']
    not_if "grep rbenv ~/.bash_profile", :environment => { :'HOME' => "/home/#{node['ruby-env']['user']}"  }
end

directory "/home/#{node['ruby-env']['user']}/.rbenv/plugins" do
    owner node['ruby-env']['user']
    group node['ruby-env']['group']
    mode 0755
    action :create
end

git "/home/#{node['ruby-env']['user']}/.rbenv/plugins/ruby-build" do
    repository node["ruby-env"]["ruby-build_url"]
    action :sync
    user node['ruby-env']['user']
    group node['ruby-env']['group']
end

execute "rbenv install #{node['ruby-env']['version']}" do
    command "/home/#{node['ruby-env']['user']}/.rbenv/bin/rbenv install #{node['ruby-env']['version']}"
    user node['ruby-env']['user']
    group node['ruby-env']['group']
    environment 'HOME' => "/home/#{node['ruby-env']['user']}"
    not_if { File.exists?("/home/#{node['ruby-env']['user']}/.rbenv/versions/#{node['ruby-env']['version']}")}
end


execute "rbenv global #{node['ruby-env']['version']}" do
    command "/home/#{node['ruby-env']['user']}/.rbenv/bin/rbenv global #{node['ruby-env']['version']}"
    user node['ruby-env']['user']
    group node['ruby-env']['group']
    environment 'HOME' => "/home/#{node['ruby-env']['user']}"
    not_if { File.exists?("/home/#{node['ruby-env']['user']}/.rbenv/versions/#{node['ruby-env']['version']}")}
end


execute "rbenv shell #{node['ruby-env']['version']}" do
    command "/home/#{node['ruby-env']['user']}/.rbenv/bin/rbenv shell #{node['ruby-env']['version']}"
    user node['ruby-env']['user']
    group node['ruby-env']['group']
    environment 'HOME' => "/home/#{node['ruby-env']['user']}"
    not_if { File.exists?("/home/#{node['ruby-env']['user']}/.rbenv/versions/#{node['ruby-env']['version']}")}
end

3.attributeファイルの作成。

$ vim site-cookbooks/ruby-env/attributes/default.rb
default['ruby-env']['user'] = "vagrant"
default['ruby-env']['group'] = "vagrant"
default['ruby-env']['version'] = "2.3.1"
default['ruby-env']['rbenv_url'] = "https://github.com/sstephenson/rbenv"
default['ruby-env']['ruby-build_url'] = "https://github.com/sstephenson/ruby-build"

※EC2にデプロイする場合は以下のようにuserとgroupの内容をec2-userにする。

default['ruby-env']['user'] = "ec2-user"
default['ruby-env']['group'] = "ec2-user"
default['ruby-env']['version'] = "2.3.1"
default['ruby-env']['rbenv_url'] = "https://github.com/sstephenson/rbenv"
default['ruby-env']['ruby-build_url'] = "https://github.com/sstephenson/ruby-build"

4..bash_profileのテンプレートファイルの作成

$ vim site-cookbooks/ruby-env/template/default/.bash_profile.erb
bash_profile.erb
# .bash_profile

if [ -f ~/.bashrc] ; then
    . ~/.bashrc
fi

PATH=$PATH:$HOME/bin
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

Node.jsのレシピ

1.Cookbookの作成。

$ bundle exec knife cookbook create nodejs -o ./site-cookbooks

2.Recipeファイルの作成。

$ vim site-cookbooks/nodejs/recipes/default.rb
bash "install nodejs" do
    user "root"
    cwd "/tmp"
    code <<-EOC
        curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -
        yum -y install gcc-c++ make nodejs
     EOC
end

Berkshelfの実行 + Vagrantのプロビジョニング

Berkshelfを実行して、Vagrantでプロビジョニングを実行する。

$ bundle exec berks vendor ./cookbooks
$ vagrant reload
$ vagrant provision

bundle exec berks install --path ./cookbooksはdeprecated

Railsの動作確認

ホスト側でRailsをインストールする。

$ vagrant ssh
# rbenv shell 2.3.1
$ gem install rails -V
$ rails new sample --skip-bundle
$ cd sample/
$ mkdir -p shared/{pids,log}

Gemfileを開いてgem 'unicorn'の一行を追加する。

vim Gemfile
source 'https://rubygems.org'


gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
gem 'sqlite3'
gem 'puma', '~> 3.0'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.2'
gem 'jquery-rails'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'

gem 'unicorn'

group :development, :test do
  gem 'byebug', platform: :mri
end

group :development do
  gem 'web-console'
  gem 'listen', '~> 3.0.5'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

以下のコマンドで上記の内容をインストール。

bundle install

unicornの設定ファイルを以下のように編集する。

vim config/unicorn.rb
listen "/tmp/unicorn.sock"
worker_processes 2

pid "/home/vagrant/sample/shared/pids/unicorn.pid"
stderr_path "/home/vagrant/sample/shared/log/unicorn.log"
stdout_path "/home/vagrant/sample/shared/log/unicorn.log"
cd sample/ 
bundle exec unicorn -c config/unicorn.rb [-D]

UnicornではなくPumaで動かす場合は以下のコマンド

bundle exec rails s (-e production) -b '0.0.0.0'

 http://192.168.33.10:3000 にアクセスすると以下のような画面が現れる。

スクリーンショット 2016-11-17 0.59.18.png

Unicornのプロセスの終了(-Dでデーモンで立ち上げた場合)

kill -QUIT `cat /home/vagrant/test_unicorn/shared/pids/unicorn.pid`

OpsWorksでデプロイ

Berkshelfによるパッケージ化

以下のコマンドを実行して、S3にアップロードする。

$ bundle exec berks package cookbooks.tar.gz
$ aws s3 cp cookbooks.tar.gz s3://sample-bucket/

OpsWorks(OpsWorks Stacks)の操作

  1. マネジメントコンソールからOpsWorksを選択
  2. [Go to OpsWorks Stacks]を選択。

Stackの設定

  1. [Stacks]から[Add stack]を選択。
  2. [Chef 12 stack]タブを選択し、以下のように入力する。

Stack name: sample-stack
Region: Asia Pacific (Tokyo)
VPC: vpc-xxxxxxxx (default)
Default subnet: xxx.xxx.xxx.xxx/xx – ap-northeast-1a
Default operating system: Linux, Amazon Linux 2016.09
Default SSH key: Do not use a default SSH key
Chef version: 12
Use custom Chef cookbooks: Yes
Repository type: S3 Archive
Repository URL: http://.s3-website-ap-northeast-1.amazonaws.com/cookbooks.tar.gz
Access key ID: AKIAXXXXXXXXXXXXXXXXXXXX
Secret access key: xxxxxxxxxxxxxxxxxxxxxxxxx
Stack color: blue (default)

Advanced optionsは以下のような項目がある。
Default root device type: EBS backed
IAM role:
Default IAM instance profile:
API endpoint region: ap-northeast-1a (REGIONAL)
Hostname theme: Layer Dependent
OpsWorks Agent version: 4021(Dec 16th 2016)
Custom JSON: (空欄)
Use OpsWorks security groups: Yes

Layerの設定

  1. [Layers]の設定
  2. [Add layer]を選択し、以下のように入力する。
    Name: sample-layer
    Short name: sample

[Add layer]を選択。

  1. 作成したLayerを選択し、[Recipes]を選択。
  2. Setupに以下の5つを追加する。
  • nginx::default
  • nodejs::default
  • ruby-env::default
  • yum::default
  • yum-epel::default

[Save]を選択。

※これを忘れた場合、Chefによるプロビジョニングが行われずに、後述のインスタンスが起動してしまう。

Instanceの作成

1.[Instances]を選択。
2.[+ Instance]を選択し、以下のように入力する。

Hostname: sample
Size: t2.micro
Subnet: XXX.XXX.XXX.XXX/XX – ap-northeast-1a

[Add Instance]を選択。

3.作成したインスタンスのActionsから[start]を選択。以降プロビジョニングが始まる。もし、起動に失敗した場合は、Hostnameのホスト名を選択した時に遷移するページの最下部にあるLogsから確認出来るLogを確認する。

補足

Berkshelfの実行

bundle exec berks install --path ./cookbooks

これはdeprecatedなので以下を実行

bundle exec berks vendor --path ./cookbooks

注意点

ホスト側でgemコマンドが見つからない

rbenv shell 2.3.1

を実行することでgemを認識するようになる。
http://qiita.com/kasumani/items/042bf799d6794bd2e4f2

Ansible

導入

インストール

brew install ansible

試しにホストに疎通確認。

[sample]
<IP address> ansible_user=ec2-user ansible_ssh_private_key_file=~/.ssh/id_rsa
$ ansible -i hosts all -m ping
<IP address> | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}

Ansible Playbookによるプロビジョニング

ディレクトリ構成

.
├── ansible.cfg
├── group_vars
│   └── sample
├── inventory
│   └── hosts
├── sample.yml
└── roles
    ├── nginx
    │   ├── defaults
    │   │   └── main.yml
    │   ├── handlers
    │   │   └── main.yml
    │   ├── tasks
    │   │   └── main.yml
    │   └── templates
    │       ├── 404.html.j2
    │       ├── 50x.html.j2
    │       ├── index.html.j2
    │       └── nginx.conf.j2
    ├── rails
    │   ├── defaults
    │   │   └── main.yml
    │   └── tasks
    │       └── main.yml
    ├── rbenv
    │   └── tasks
    │       └── main.yml
    └── yum
        └── tasks
            └── main.yml

ファイル内容

sample.yml
- hosts: sample
  become: yes 
  roles:
    - yum 
    - rbenv
    - rails
    - nginx
ansible.cfg
[defaults]
remote_user=ec2-user
private_key_file=~/.ssh/id_rsa
inventory=./inventory/hosts
executable = /bin/bash -l
  • inventory
[defualts]
<IP address>
  • group_vars
rbenv_user: ec2-user
rbenv_ruby_version: 2.3.1
  • roles

    • yum
yum
└── tasks
    └── main.yml
main.yml
---
- name: Install package for bundle install
  yum: name={{ item }} state=latest
  with_items:
    - sqlite-devel
    - gcc-c++
- name: Update all packages
  yum: name=* state=latest

 gcc-c++はtherubyracerのインストールに必要

  • rbenv
rbenv
└── tasks
    └── main.yml
---
- name: Install dependencies for rbenv
  yum: name={{ item }} state=latest
  with_items:
    - git

- name: Install rbenv
  become: yes
  become_user: "{{ rbenv_user }}"
  git: repo=https://github.com/sstephenson/rbenv.git dest=~/.rbenv

- name: Add ~.rbenv/bin to PATH
  become: yes
  become_user: "{{ rbenv_user }}"
  lineinfile: >
    dest="~/.bash_profile"
    line="export PATH=$HOME/.rbenv/bin:$PATH"
- name: Eval rbenv init in ~/.bash_profile
  become: yes
  become_user: "{{ rbenv_user }}"
  lineinfile: >
    dest="~/.bash_profile"
    line='eval "$(rbenv init -)"'

- name: Install dependencies for ruby-build (see. https://github.com/sstephenson/ruby-build/wiki)
  yum: name={{ item }} state=latest
  with_items:
    - gcc
    - openssl-devel
    - libyaml-devel
    - libffi-devel
    - readline-devel
    - zlib-devel
    - gdbm-devel
    - ncurses-devel

- name: Install ruby-build as rbenv plugin
  become: yes
  become_user: "{{ rbenv_user }}"
  git: repo=https://github.com/sstephenson/ruby-build.git dest=~/.rbenv/plugins/ruby-build

- name: Check if version is installed ruby
  become_method: yes
  become_user: "{{ rbenv_user }}"
  shell: "rbenv versions | grep {{ rbenv_ruby_version }}"
  register: rbenv_check_install
  changed_when: False
  ignore_errors: yes

- name: Install ruby
  become_method: yes
  become_user: "{{ rbenv_user }}"
  shell: "rbenv install {{ rbenv_ruby_version }}"
  when: rbenv_check_install|failed

- name: Check if version is the default ruby version
  become_method: yes
  become_user: "{{ rbenv_user }}"
  shell: "rbenv version | grep {{ rbenv_ruby_version }}"
  register: rbenv_check_default
  changed_when: False
  ignore_errors: yes

- name: Set default ruby version
  become_method: yes
  become_user: "{{ rbenv_user }}"
  shell: "rbenv global {{ rbenv_ruby_version }}"
  when: rbenv_check_default|failed

※注意点で、rbenvのコマンドのパスを通すところで、.bash_profileに設定を追記しているが、sourceコマンドでは反映されない。なので、パスを適用させるために、シェルはログインシェルとして実行することで解決できる。具体的には、ansible.cfg中でexecutable = /bin/bash -lを宣言すると良い。

  • rails
rails
├── defaults
│   └── main.yml
└── tasks
    └── main.yml
  • defaults
main.yml
---
railsenv_deploy_dir: "/var/www/sample"
railsenv_deploy_user: ec2-user
railsenv_deploy_group: ec2-user
  • tasks
main.yml
---
- name: Install mysql-devel
  yum: name={{ item }} state=latest
  with_items:
    - mysql-devel
    - gcc-c++

- name: Install dependencies for nokogiri
  yum: name={{ item }} state=latest
  with_items:
    - patch

- name: Install bundler
  become_user: "{{ rbenv_user }}"
  gem: name={{ item }} user_install=False
  with_items:
    - bundler

- name: Create deploy directory
  file: path={{ railsenv_deploy_dir }} state=directory owner={{ railsenv_deploy_user }} group={{ railsenv_deploy_group }} mode=0755
  • nginx
nginx
├── defaults
│   └── main.yml
├── handlers
│   └── main.yml
├── tasks
│   └── main.yml
└── templates
    ├── index.html.j2
    └── nginx.conf.j2
  • tasks
main.yml
---
- name: Install nginx
  yum: name=nginx state=latest

- name: Set nginx service to start on boot
  service: name=nginx enabled=true

- name: Put nginx.conf
  template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf backup=true mode=0644
  notify: restart nginx

- name: Put share index.html
  template: src=index.html.j2 dest=/usr/share/nginx/html/index.html mode=644
  • handlers
main.yml
---
- name: restart nginx
  service: name=nginx state=restarted
  • defualts
main.yml
---
nginx_woker_processes: "auto"
nginx_server_tokens: "off"
nginx_includes: []
  • templates
index.html.j2
<html>
    <body>
        <h1>Hello, world!</h1>
    </body>
</html>
nginx.conf.j2
user  nginx;
worker_processes  {{ nginx_woker_processes }};

error_log  /var/log/nginx/error.log;

pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


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

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

    error_log  /var/www/sample/log/nginx.error.log; 
    access_log /var/www/sample/log/nginx.access.log; 

    server_tokens {{ nginx_server_tokens }};

    sendfile        on;

    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
{% for include in nginx_includes %}
    include {{ include }};
{% endfor %}

    index   index.html index.htm;

    client_max_body_size 2G;
    upstream app_server {
        server unix:/var/www/sample/tmp/sockets/.unicorn.sock fail_timeout=0;
    }

   server {
        listen 80;
        server_name <IP address>;

        keepalive_timeout 5;
        root /var/www/sample/public;
        try_files $uri/index.html $uri.html $uri @app;
        location @app {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass http://app_server;
        }
    }
}

実行

ansible-playbook sample.yml

ansible.cfg中でremote_user, private_key_file, inventoryの設定を追加しない場合は以下のようにコマンドを打つ必要がある。

ansible-playbook -i "<IP address>," --user=ec2-user --private-key=~/.ssh/id_rsa sample.yml

注意点

  • rbenvの設定ファイルを.bash_profileの内容がsourceでは反映されないのでshellモジュールを使う時は必ずログインシェルとして実行する
[defaults]
executable = /bin/bash -l
- name: Sample shell execution
  become_method: yes
  become_user: ec2-user
  shell: "~~~"

http://www.bunkei-programmer.net/entry/2015/05/16/162020

  • therubyracerの依存パッケージでgcc-c++をyumで入れる。

Docker [Docker Compose]

FROM ruby:2.3.1

ENV APP_ROOT /app

RUN apt-get update -qq && 
    apt-get install -y build-essential 
                       libmysqld-dev 
                       libmysqlclient-dev 
                       mysql-client 
                       --no-install-recommends && 
    rm -Rf /var/lib/opt/lists/*

RUN mkdir $APP_ROOT
WORKDIR $APP_ROOT

ADD . /app
RUN bundle install
docker-compose.yml
version: '2' 
services:
  app:
    build: .
    command: bundle exec rails s -b '0.0.0.0'
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    links:
      - db
  db: 
    image: mysql:5.6.30
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root

ただし、Gemfile中でtherubyracerを使用しない場合は、Dockerfile中でapt-get installnodejsもインストールする。

ビルド & 実行

以下のコマンドはターミナルのウィンドウを新たに開くたびに実行する。

docker-machine start default
eval $(docker-machine env defualt)

ビルド

docker-compose build

実行

docker-compose up

IPアドレスを以下のコマンドで調べる。

docker-machine ip

192.168.99.100:3000のようにブラウザにアクセスする。

ECSでデプロイ

ECSでデプロイする場合は別投稿の下記のURLを参考にして下さい。
http://qiita.com/hayashier/items/b34f82c42053f85e5b09

マニュアルで環境構築

Railsの環境準備

sudo yum install -y 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 -y 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 install 2.3.1
rbenv global 2.3.1
ruby -v

Webサーバのインストール

sudo yum install gem
gem install bundle
bundle -v
rake secret
sudo yum install nginx
sudo service nginx start

以下、Capistrano等のデプロイツールを用いてデプロイする場合は必ずしも必要ではありません。

gitの準備

vim ~/.netrc
cd ~/.ssh
ssh-keygen -t rsa -C "<メールアドレス>"
ssh-add ~/.ssh/id_rsa
cat id_rsa_admin.pub 
ssh -T git@github.com

GitHubに公開鍵の登録をする。

  • .netrc
machine github.com
login <GitHubユーザ名>
password xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Webサーバの環境・設定

cd /var/www/
sudo git clone https://github.com/<GitHubユーザ名>/<レポジトリ名>.git
sudo chown -R ec2-user:ec2-user <レポジトリ名>/
cd <レポジトリ名>/
bundle install --path ./vendor/bundle/ 
sudo vim /etc/nginx/conf.d/admin.conf 
sudo service nginx restart
bundle exec rake secret
cat config/secrets.yml 
vim .env
source .env
echo $SECRET_KEY_BASE
sudo service nginx restart
rails s -e production
  • .env
export DATABASE_PASSWORD_PRODUCT=xxxxxxxxxxxxxxx

config/database.yml中のデータベースへのパスワードを以下のように環境変数として定義しておき、.env中でインポートする。

<%= ENV[‘DATABASE_PASSWORD_PRODUCT’] %>

  • sample.conf
error_log  /var/www/sample/log/nginx.error.log; 
access_log /var/www/sample/log/nginx.access.log;

client_max_body_size 2G;
upstream app_server {
  server unix:/var/www/sample/tmp/sockets/.unicorn.sock fail_timeout=0; 
}
server {
  listen 80;
  server_name <IPアドレス>;
  # nginx so increasing this is generally safe...
  keepalive_timeout 5;
  # path for static files
  root /var/www/sample/public; 
  # page cache loading
  try_files $uri/index.html $uri.html $uri @app;
  location @app {
    # HTTP headers
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }     
  # Rails error pages
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /var/www/sample/public; 
  }
}

参考

Chef

Ansible

Docker

https://docs.docker.com/compose/rails/

続きを読む

AWS EC2のメモリ利用率をCloudWatchに追加する

EC2を立ち上げ後、デフォルトではメモリ利用率はCloudWatchに監視追加できません。
そのため、必要なパッケージをインストールし、CloudWatchモニタリング用スクリプトを設置してCronを仕掛ける必要があります。
Cronを実行するとただちCloudWatch側に反映するので比較的簡単です。
※AWSのアクセスキーID、シークレットキーの取得は別途行ってください。

必要なパッケージをインストール

$ sudo yum -y install perl-DateTime perl-Sys-Syslog perl-LWP-Protocol-https perl-Digest-SHA

CloudWatchモニタリング用スクリプトの格納場所を作成

$ sudo mkdir /usr/local/cloudwatch
$ cd /usr/local/cloudwatch
$ sudo wget http://aws-cloudwatch.s3.amazonaws.com/downloads/CloudWatchMonitoringScripts-1.2.1.zip
$ sudo unzip CloudWatchMonitoringScripts-1.2.1.zip
$ sudo rm CloudWatchMonitoringScripts-1.2.1.zip

スクリプトの設定

$ cd aws-scripts-mon
$ sudo cp awscreds.template awscreds.conf
$ sudo vim awscreds.conf

「awscreds.conf」に、以下を記載

  • AWSAccessKeyId=[アクセスキーID]
  • AWSSecretKey=[シークレットキー]
    ※アクセスキーID、シークレットキーの取得は別途行ってください。

Cron登録

$ crontab -e

以下をcrontabに登録

*/5 * * * * /usr/local/cloudwatch/aws-scripts-mon/mon-put-instance-data.pl --mem-util --mem-used --mem-avail --from-cron

AWSコンソールからCloudWatchで確認する

AWSコンソールでCloudWatchを選択すると、メトリクスの中に「Linuxシステム」の欄が追加されていることがわかる。
20161207_aws_ec2_memory_metrix.png

「Linuxシステム」-「InstanceId」をドリルダウンすると、「MemoryUtilization」、「MemoryUsed」、「MemoryAvailable」の項目が取れていることがわかる。
これらを組み合わせてグラフを作成し、ダッシュボードに表示できる。
20161213_aws_ec2_memory_5stack.png

まとめ

メモリ監視でアラームを追加したり、ほかにもDisk容量監視も同じ要領で追加できます。

Disk容量監視は以下のcronを追加するだけ

*/5 * * * * /usr/local/cloudwatch/aws-scripts-mon/mon-put-instance-data.pl --disk-space-util --disk-path=/ --from-cron

参考にしたサイト

AWS EC2でメモリ利用率をCloud Watchで監視する
AWS EC2でメモリ利用率とディスク容量をCloudWatchで監視する

上記のサイトだと、パッケージperl-Digest-SHAが入っていないエラーが発生したので下記サイトを参考にエラーを取り除いた。
RedmineとGitを連携させる(認証統合)

続きを読む

AWS EC2にMongoDBインストールとレプリケーション設定

MongoDBインストールとレプリケーション設定について、簡易的な抜粋メモとして書いています。
詳細に関しては、記事の最後の参考サイトを確認するようにしてください。

◆ 今日やること

  • MongoDBをEC2にインストール
  • レプリケーションの設定と確認
    • 今回せっていするレプリケーションの形式は、以下の図の通り、「Primary with Secondary Members」です。

mongorepl.png
引用元:Replication — MongoDB Manual 3.4

◆ バージョン

  • MongoDB 3.2
  • Linux 4.4.30-32.54.amzn1.x86_64 #1 SMP Thu Nov 10 15:52:05 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

◆ 実装編

> EC2へのインストール

sudo yum update -y
sudo vim /etc/yum.repos.d/mongodb-org-3.2.repo

[mongodb-org-3.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/amazon/2013.03/mongodb-org/3.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.2.asc
sudo yum install -y mongodb-org
sudo service mongod start

sudo chkconfig mongod on

> 設定周り

  • WoredTigerストレージエンジンの主な設定

    • cacheSizeGB: システムメモリの6、7割
    • blockCompressor:デフォルトはsnappy(中間です)
/etc/mongod.conf
# Where and how to store data.
storage:
  dbPath: /data
  journal:
    enabled: true
  wiredTiger:
    engineConfig:
       cacheSizeGB: 1
       journalCompressor: snappy
    collectionConfig:
       blockCompressor: snappy

> EBSボリュームをAttach

  • EBSボリュームにmongoのデータを溜めるようにする。

Amazon EBS ボリュームを使用できるようにする – Amazon Elastic Compute Cloudに従って、EBSボリュームをアタッチする

  • パーミッションを変更するのを忘れないように。
sudo chown -R mongod:mongod /data
  • /etc/mongod.conf
/etc/mongod.conf
# Where and how to store data.
storage:
  dbPath: /data

> レプリケーションの設定

MongoDBではマスターのことをプライマリ,スレーブのことをセカンダリと呼びます。
MongoDBのレプリケーションの最小構成は,3つのノードが必要です。

  • ネットワークインターフェイスの設定で、レプリケーションを組むサーバのIPを記述しておくこと

レプリケーション設定前には、お互いに通信できることを確認しないといけません
Troubleshoot Replica Sets — MongoDB Manual 3.4

mongodb が listen するIPアドレスはデフォルトでは 127.0.0.1 に設定されており、ローカルからのみアクセスを許可するようになっています
mongod.conf の bind_ip に設定されたIPアドレスで listen するのでこれを変更することで外部からの接続を許可します
ここに 0.0.0.0 を設定すると全てのIPアドレスからの接続を許可します

/etc/mongod.conf
# network interfaces
net:
  port: 27017
  bindIp: [127.0.0.1,10.1.52.111,10.1.51.227,10.1.51.68]


  • レプリケーション名を決める
  • Oplogのサイジングと設定サイズを決める
/etc/mongod.conf
replication:
   oplogSizeMB: 500
   replSetName: testRepl

第4回 MongoDBのレプリケーションを構築してみよう:MongoDBでゆるふわDB体験|gihyo.jp … 技術評論社

OplogはCapped Collectionなので,作成時以外にサイズ変更することができません。 デフォルトのOplogのサイズは「1GBまたは,ディスクの空き領域の5%」
Staleを避けるために,Oplogのサイジングは非常に重要となります。
Oplogのサイズは,mongodの初回起動時にoplogSizeオプションで変更可能です。
Oplogの適切なサイズを見積もる方法のひとつに,本番を想定した書き込みテストを実施し,作成されたOplogのサイズを取得する方法があります。
1時間程度,本番想定と同程度の書き込みテストを行った後,以下のコマンドでレプリケーションの最新情報を取得します。

> db.getReplicationInfo()

1時間で作成されたOplogのサイズがわかれば,Oplogのサイジングの目安となります。
少なくとも8時間のセカンダリのダウンタイムに耐えられるようにしておくことが推奨されています。

  • レプリケーションの設定

どれかのサーバに入って、以下のコマンドを実行

config = {
  _id : "testRepl",
  members : [
    { _id : 0, host : "10.1.51.227:27017" },
    { _id : 1, host : "10.1.51.68:27017" },
    { _id : 2, host : "10.1.52.111:27017"} ] }

rs.initiate(config)
  • スレーブの読み取り専用動作設定

そのままだと、スレーブが読み取り専用としてアクセスできないので以下の設定を行っておく。

スレーブノードで、以下のコマンドを実行

 db.getMongo().setSlaveOk()
  • レプリケーションステータスの確認
testRepl:PRIMARY> rs.status();
{
    "set" : "connobaRepl",
    "date" : ISODate("2017-01-12T07:03:05.556Z"),
    "myState" : 1,
    "term" : NumberLong(6),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "members" : [
        {
            "_id" : 0,
            "name" : "10.1.51.227:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 100310,
            "optime" : {
                "ts" : Timestamp(1484182286, 1),
                "t" : NumberLong(6)
            },
            "optimeDate" : ISODate("2017-01-12T00:51:26Z"),
            "electionTime" : Timestamp(1484104344, 1),
            "electionDate" : ISODate("2017-01-11T03:12:24Z"),
            "configVersion" : 3,
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "10.1.51.68:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 100245,
            "optime" : {
                "ts" : Timestamp(1484182286, 1),
                "t" : NumberLong(6)
            },
            "optimeDate" : ISODate("2017-01-12T00:51:26Z"),
            "lastHeartbeat" : ISODate("2017-01-12T07:03:04.017Z"),
            "lastHeartbeatRecv" : ISODate("2017-01-12T07:03:04.020Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "10.1.51.227:27017",
            "configVersion" : 3
        },
        {
            "_id" : 2,
            "name" : "10.1.52.47:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 99751,
            "optime" : {
                "ts" : Timestamp(1484182286, 1),
                "t" : NumberLong(6)
            },
            "optimeDate" : ISODate("2017-01-12T00:51:26Z"),
            "lastHeartbeat" : ISODate("2017-01-12T07:03:05.025Z"),
            "lastHeartbeatRecv" : ISODate("2017-01-12T07:03:05.022Z"),
            "pingMs" : NumberLong(2),
            "syncingTo" : "10.1.51.227:27017",
            "configVersion" : 3
        }
    ],
    "ok" : 1
}

> mongoログインの時の警告メッセージを消す方法

[ec2-user@ip-10-1-52-47 ~]$ mongo
MongoDB shell version: 3.2.11
connecting to: test
Server has startup warnings:
2017-01-11T03:10:18.308+0000 I CONTROL  [initandlisten]
2017-01-11T03:10:18.308+0000 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2017-01-11T03:10:18.308+0000 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2017-01-11T03:10:18.308+0000 I CONTROL  [initandlisten]
testRepl:SECONDARY>

◆ 参考サイト

続きを読む

StackStormでNew Relicと連携してみました

image

はじめに

前回はAWSにStackStormをインストールしたので、今回はアプリケーション監視ツールであるNew Relicと連携できるようにしたいと思います。
StackStormには、Packと呼ばれるソフトウェアコンポーネントの追加機能があり、New RelicのPackも用意されています。今回はPackのインストールから使い方まで紹介したいと思います。

New Relicに関しては、下記記事を参考にしてください。
New RelicのAPMとINFRASTRUCTUREを使って監視してみました(前半)
New RelicのAPMとINFRASTRUCTUREを使って監視してみました(後半)

StackStomの設定

New Relic Packのインストール

下記st2コマンドでNewRelicのPackをインストールします。statusがsucceededになっていたら成功です。

$ st2 run packs.install packs=newrelic
......................
id: 586df3da897d4405b3b07f36
action.ref: packs.install
parameters:
  packs:
  - newrelic
status: succeeded
start_timestamp: 2017-01-05T07:20:58.022719Z
end_timestamp: 2017-01-05T07:23:13.100056Z
+--------------------------+-------------------------+--------------------------+-------------------------+------------------------------+
| id                       | status                  | task                     | action                  | start_timestamp              |
+--------------------------+-------------------------+--------------------------+-------------------------+------------------------------+
| 586df3da897d440342b8b6ba | succeeded (7s elapsed)  | download                 | packs.download          | Thu, 05 Jan 2017 07:20:58    |
|                          |                         |                          |                         | UTC                          |
| 586df3e2897d440342b8b6bd | succeeded (3s elapsed)  | virtualenv_prerun        | packs.virtualenv_prerun | Thu, 05 Jan 2017 07:21:06    |
|                          |                         |                          |                         | UTC                          |
| 586df3e5897d440342b8b6c0 | succeeded (56s elapsed) | setup_virtualenv         | packs.setup_virtualenv  | Thu, 05 Jan 2017 07:21:09    |
|                          |                         |                          |                         | UTC                          |
| 586df41e897d440342b8b6c4 | succeeded (63s elapsed) | reload                   | packs.load              | Thu, 05 Jan 2017 07:22:06    |
|                          |                         |                          |                         | UTC                          |
| 586df45e897d440342b8b6c8 | succeeded (2s elapsed)  | restart-sensor-container | packs.restart_component | Thu, 05 Jan 2017 07:23:10    |
|                          |                         |                          |                         | UTC                          |
+--------------------------+-------------------------+--------------------------+-------------------------+------------------------------+

New Relic Packの確認

1) センサー確認

$ st2 sensor list
+---------------------------------+----------+---------------------------------+---------+
| ref                             | pack     | description                     | enabled |
+---------------------------------+----------+---------------------------------+---------+
| newrelic.NewRelicHookSensor     | newrelic | Sensor which watches for alerts | True    |
|                                 |          | from NewRelic API.              |         |
| newrelic.LegacyNewRelicHookSens | newrelic | Sensor which watches for alerts | False   |
| or                              |          | from the NewRelic legacy API.   |         |
+---------------------------------+----------+---------------------------------+---------+

2)トリガー確認

$ st2 trigger list
+--------------------------------------+----------+-------------------------------------------+
| ref                                  | pack     | description                               |
+--------------------------------------+----------+-------------------------------------------+
| newrelic.WebAppAlertTrigger          | newrelic |                                           |
| newrelic.WebAppNormalTrigger         | newrelic |                                           |
| newrelic.ServerAlertTrigger          | newrelic |                                           |
| newrelic.ServerNormalTrigger         | newrelic |                                           |
+--------------------------------------+----------+-------------------------------------------+

3)アクション確認

$ st2 action list | grep new
+---------------------------------+----------+-------------------------------------------+
| ref                             | pack     | description                               |
+---------------------------------+----------+-------------------------------------------+
| newrelic.get_alerts             | newrelic | Get alerts for app.                       |
| newrelic.get_metric_data        | newrelic | Get metric data for metric.               |

New Relic Packの設定

1) 設定ファイルコピー
New RelicのPackは、下記フォルダーに保存されるので、

/opt/stackstorm/packs/newrelic/

サンプルファイルから設定ファイルを作成します

cd /opt/stackstorm/configs
cp /opt/stackstorm/packs/newrelic/newrelic.yaml.example ./newrelic.yaml

2) 設定ファイルの編集
デフォルトだとBase URLが、http://x.x.x.x:10001/st2/nrhook/になります

$vim ./newrelic.yaml
---
api_url: "https://api.newrelic.com/v2/"
api_key: "<New RelicのAPIキーを入力します>"
sensor_config:
    host: "0.0.0.0"
    port: 10001
    url: "/st2/nrhook/"
    normal_report_delay: 300

3) st2 再起動
設定を有効にするためにStackStormを再起動します

$ st2ctl reload

4) TCPポート起動確認
New RelicのSenserが起動すると、TCPの10001番でListenします

$ netstat -ln | grep 10001
tcp        0      0 0.0.0.0:10001           0.0.0.0:*               LISTEN

ルール作成

New Relicから送信されたアラート内容を、ファイル(st2.newrelic.out)に出力するルールを作成

1) APMアラート用ルール作成

$ cd /opt/stackstorm/packs/newrelic/rules/
$ vim webapp_notify.yaml
---
name: "webapp.notify"
pack: "newrelic"
description: "WebAppAlertTrigger Sample rule for newrelic."
enabled: true

trigger:
    type: "newrelic.WebAppAlertTrigger"

criteria: {}

action:
    ref: "core.local"
    parameters:
        cmd: "echo {{ trigger.alert }} >> ~/st2.newrelic.out"

2) Serversアラート用ルール作成

$ vim server_notify.yaml
---
name: "server.notify"
pack: "newrelic"
description: "ServerAlertTrigger Sample rule for newrelic."
enabled: true

trigger:
    type: "newrelic.ServerAlertTrigger"

criteria: {}

action:
    ref: "core.local"
    parameters:
        cmd: "echo {{ trigger.alert }} >> ~/st2.newrelic.out"

3)st2ルール作成

st2 rule create /opt/stackstorm/packs/newrelic/rules/webapp_notify.yaml
st2 rule create /opt/stackstorm/packs/newrelic/rules/server_notify.yaml

4)st2ルール確認

$ st2 rule list
+------------------------+----------+-----------------------------------------+---------+
| ref                    | pack     | description                             | enabled |
+------------------------+----------+-----------------------------------------+---------+
| newrelic.server.notify | newrelic | ServerAlertTrigger Sample rule for      | True    |
|                        |          | newrelic.                               |         |
| newrelic.webapp.notify | newrelic | WebAppAlertTrigger Sample rule for      | True    |
|                        |          | newrelic.                               |         |
+------------------------+----------+-----------------------------------------+---------+

動作確認

StackStomから自分宛(127.0.0.1)にテスト用のアラートを送信して確認します。

1) APMアラート確認用

curl -X POST 
    -H 'Content-Type:application/json; charset=UTF-8' 
    -d '{ 
    "current_state":"open",
    "details":"Apdex < .50 for at least 10 min",
    "severity":"CRITICAL",
    "event_type":"INCIDENT",
    "targets":[{
        "product": "APM", 
        "name": "TEST APM",
        "labels": {}, 
        "type": "Application", 
        "id": "123456789"
        }]
    }' 
http://127.0.0.1:10001/st2/nrhook/

2) Serversアラート確認用

curl -X POST 
    -H 'Content-Type:application/json; charset=UTF-8' 
    -d '{ 
    "current_state":"open",
    "details":"System load > 0.1 for at least 5 minutes",
    "severity":"CRITICAL",
    "event_type":"INCIDENT",
    "targets":[{
        "product": "SERVERS", 
        "name": "TEST Servers",
        "labels": {}, 
        "type": "Server", 
        "id": "123456789"
        }]
    }' 
http://127.0.0.1:10001/st2/nrhook/

3) アラート内容の確認

$ tail /home/stanley/st2.newrelic.out
{uevent_type: uINCIDENT, udetails: uApdex < .50 for at least 10 min, ucurrent_state: uopen, utargets: [{uproduct: uAPM, ulabels: {}, utype: uApplication, uname: uTEST APM, uid: u123456789}], useverity: uCRITICAL}
{uevent_type: uINCIDENT, udetails: uSystem load > 0.1 for at least 5 minutes, ucurrent_state: uopen, utargets: [{uproduct: uSERVERS, ulabels: {}, utype: uServer, uname: uTEST Servers, uid: u123456789}], useverity: uCRITICAL}

AWSの設定

1)セキュリテイ―グループの追加
外部(New Relic)からTCP:10001の接続ができるようセキュリテイ―グループを変更
New Relic DocumentのWebhook alerts項目にアラートの送信元IPが記載されているので、必要であれば送信元を制限します。

Webhook alerts
New Relic-generated webhooks for alert policies will be sent from an IP address in the 50.31.164.0/24 or 162.247.240.0/22 network block.

New Relic Alertsの設定

1)AlertsのNotification Channelの追加
image

2)Channelの設定
StackStomの待ち受けポート(10001/tcp)に対してWebhookの設定をします。
image

3)Alert Policyの作成

image

4)Alerts Conditionの作成
アラート条件を設定します
image

5)Channelの選択
先ほど作成したStackStomのWebhookのChannelを選択し追加します。
image

これでNew RelicからのAlertsをトリガーにStackStormで連携する事ができました。

続きを読む

Vagrant による EC2 のプロビジョニング + Jupyter(IPython Notebook)on Docker

概要

(前半)Vagrantを利用してのEC2のプロビジョニング
(後半)Dockerを利用してのJupyter(IPython Notebook)の起動

初めてQiitaに書かせていただきます。
正月休みにゼロから学ぶディープラーニングと、あわよくばTensorFlowを勉強しよう、せっかくなら環境構築も勉強しよう……と試してみたので、備忘録兼ねての紹介です。
今回前後半は独立しているので片方のみの参照も可能かと思います。

:)

  • EC2なのでスペックの上げ下げが簡単
  • VagrantでEC2のStart/Stopが簡単に -> 節約に
    作業後の落とし忘れミスを最小限にしてくれます。
  • Dockerで可搬性向上
    ここは勉強兼ね。データ分析環境をあちこちに立てるのに便利、という印象。

:(

  • EC2にお金がかかる
  • Dockerを噛ませる意味合いが薄い
    書籍の勉強のように最初から環境決め打ちなら、Ansibleなどでシンプルかつ全体を通しで、環境構築できる気がします。

構築手順

前提として

  • Vagrantのインストール
  • EC2の起動権を持つIAMユーザーのクレデンシャルの設定
  • EC2を起動するVPCやサブネットの設定

が必要です。

こちらが参考になるかと思います。

(前半)Vagrantを利用してのEC2のプロビジョニング

HashiCorpのGitHubページ: Vagrant AWS Provider を参考に設定を行います。

まずAWSを利用するためのプラグインをインストールします。

$ vagrant plugin install vagrant-aws

マシンイメージに相当するBOXですが、EC2用にダミーボックスが公開されているのでこちらを利用します。

(公式だとbox名がdummyですが、少し曖昧なのでec2と変えています。)

$ vagrant box add ec2 https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box

次に作業用のディレクトリへ移動し、Vagrantfileの作成を行います。

$ mkdir jupyter_ec2
$ cd jupyter_ec2
$ vim Vagrantfile

Vagrantfile を編集し、各種パラメータを設定します。今回はあらかじめ作成したVPCでの起動を指定しています。

他にもかなりの数のパラメータがあるようで、指定したい場合は適宜READMEや、AWS APIドキュメントを参照するのが良いかと思います。

Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "ec2" # dummy box name

  config.vm.provider :aws do |aws, override|
    # AWSクレデンシャル
    aws.aws_dir = ENV['HOME'] + "/.aws/"
    aws.aws_profile = "YOUR_PROFILE"
    aws.keypair_name = "YOUR_AWS_ACCESS_KEY"

    # SSHクレデンシャル
    override.ssh.username = "ec2-user"
    override.ssh.private_key_path = "SSH_KEY_PATH"

    # VPC設定
    aws.region = 'ap-northeast-1'
    aws.availability_zone = 'ap-northeast-1a'
    aws.subnet_id = 'subnet-XXXXXXXX'
    aws.security_groups = 'sg-XXXXXXXX'

    # EC2設定
    aws.ami = "ami-9f0c67f8" # Amazon Linux 2016.09.01
    aws.instance_type = 'm4.large'
    aws.elastic_ip = true
    aws.associate_public_ip = true
    aws.tags = { 'Name' => 'NAME_TAGS_VALUE' }

    # EBS設定
    aws.block_device_mapping = [
      {
        'DeviceName' => "/dev/xvda",
        'Ebs.VolumeSize' => 50,
        'Ebs.DeleteOnTermination' => true,
        'Ebs.VolumeType' => 'standard',
      }
    ]
  end
end

編集終了後、EC2の起動を行います。

$ vagrant up --provider=aws

うまくいかないときですが、

$ export VAGRANT_LOG=DEBUG

としたのちvagrant upを再実行すると詳細なエラーメッセージを表示してくれるようになります。

エラーメッセージを元に戻したいときはexport VAGRANT_LOG=WARN

セキュリティグループのSSHもきちんと開けているか注意しましょう。上限緩和していない場合EIP数上限も引っかかりがちです。

起動し終わったら

$ vagrant ssh

でSSHログインできるか確認します。

$ vagrant halt

でインスタンスstopが行えますので、作業しないときは適宜停止して節約に努めます。

(後半)Dockerを利用してのJupyter(IPython Notebook)の起動

Installing Dockerを参考に、まずEC2にDockerをインストールします。

$ sudo yum update -y
$ sudo yum install -y docker

docker daemonを起動し、インスタンス再起動後も自動で起動するようにしておきます。

$ sudo service docker start
$ sudo chkconfig docker on

sudo 権限なしでもdockerコマンドを追加できるようにec2-userをdockerグループに追加しておきます。

$ sudo usermod -a -G docker ec2-user

一旦exitし、再ログインすることでsudo なしでdockerが起動するようになります。

次にDockerfileの記述を行います。

$ vim Dockerfile

今回は簡単に進めるために、anacondaのイメージをベースにTensorFlowをインストールする形にします。
CMD でdocker run 時にjupyterが起動するように指定をします。

FROM continuumio/anaconda:latest

RUN mkdir /opt/notebooks
RUN pip install tensorflow
# Another installations comes here.

CMD ["/opt/conda/bin/jupyter", "notebook", "--notebook-dir=/opt/notebooks", "--ip='*'", "--port=8888", "--no-browser"]

編集終了後、Dockerfileからコンテナイメージを作成します。

$ docker build -t myjupyter .

最後にコンテナを起動します。notebookの永続化やホスト側からも覗けるようにvolumeを指定しマウントします。また、–restartオプションによりインスタンスstop/start後もdockerデーモンが自動でコンテナを立ち上げるようにします。

$ mkdir notebooks
$ docker run -p 8888:8888 -v /home/ec2-user/notebooks:/opt/notebooks --restart=always myjupyter

セキュリティグループでTCPのポート8888番の通信を許可したのち、ブラウザで[Elastic IP]:8888へアクセスすることで、Jupyterの起動を確認します。


これで勉強するときだけvagrant upしブラウザ上で学習、一旦やめるときはvagrant haltで課金停止、ローカルで試したいときは同じDockerfileからビルド、ipythonnbをVagrant経由でとってくれば簡単移植、な環境になった・・・!はずなので、書籍勉強しつつ適宜修正していこうと思います。

続きを読む

Amazon Linuxの初期設定

Amazon Linuxの初期設定の備忘録

文字コード

/etc/sysconfig/i18n
の中を

LANG=ja_JP.UTF-8

に変更し、ログインしなおす

タイムゾーン

  • /usr/share/zoneinfo/Asia/Tokyo を /etc/localtime にリンク
cp /etc/localtime /etc/localtime.bak
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
  • /etc/sysconfig/clock を編集
ZONE="Asia/Tokyo"

vim設定

set number
set encoding=utf-8
set tabstop=4
set autoindent
set expandtab
set shiftwidth=4

SFTPの操作をログに残す

http://qiita.com/Teppei1224/items/b62e5f1af335a29217e0

PHPをインストール


yum install php70.x86_64 php70-cli.x86_64 php70-common.x86_64 php70-json.x86_64 php70-mbstring.x86_64 php70-mysqlnd.x86_64 php70-pdo.x86_64 php70-process.x86_64 php70-xml.x86_64

設定

date.timezone = Asia/Tokyo
session.use_strict_mode = 1
session.gc_maxlifetime = 43200
mbstring.language = Japanese
mbstring.internal_encoding = UTF-8
expose_php = Off

MySQLをインストール

  • yumリポジトリをインストール

http://dev.mysql.com/downloads/repo/yum/

rpm -Uvh mysql57-community-release-el6-9.noarch.rpm

/etc/yum.repos.d/mysql-community.repo で、mysql57-community以外のenable=0

  • MySQLをインストール
yum install mysql-community-client mysql-community-common mysql-community-libs mysql-community-server
  • 設定
character_set_server=utf8mb4
wait_timeout=14400
interactive_timeout=14400
  • 初期パスワード
    /var/log/mysqld.log に書いてある

  • セキュリティ
    mysql_secure_installation

続きを読む

Redshiftのクラスタを再構築せざるを得なかった話

この記事は Origami Advent Calendar の20日目の記事になります。クリスマスに勤しんでおり投稿が遅くなりました。申し訳ありません。

Origamiではデータ分析用のソースにAWSのRedshiftを利用しています。
タイトルの様なデータベースを作り直すというのは普段やる事も無く、出来る事ならやりたくもないオペレーションですがAWSから届いた一通のメールにより火蓋が切られました。

メールの内容

Dear Customer,

Our systems have identified a potential hardware issue that may affect your 
Amazon Redshift cluster {your_cluster_name} the {your_region_name} AWS region.

As a precaution, we recommend that you restore a new cluster from a snapshot 
within the next 2-3 days and delete your existing cluster.

This will automatically provision you on healthy hardware and help you 
avoid an unplanned outage. Please confirm receipt of this message.

We apologize for the inconvenience, please let us know if you have any questions or concerns.

Regards,
Redshift Service Team

要約すると

潜在的なハードウェアの問題を特定したので2~3日以内に新しいクラスタをスナップショットから復元して既存のクラスタを削除してね

。。。 (゜Д゜) (゜Д゜) ハァ!?!?

いきなりこういったメールが飛んでくるのでたまったものではありませんが、兎にも角にもRedshiftが使えなくなる、あるいはハードウェアが起因でデータの信頼性が低下してしまうといった事態は避けなければなりませんのでクラスタを再構築する事になりました。
(※ここでいう信頼性とは 「データの重複、欠損がなく整合性がとれている状態」を指しています。この記事で今後記載する「信頼性」は全て同様の意味です。)

現状と構築手順の整理

とはいえ、現状のRedshiftからスナップショットを取り復元したら終わりという訳では無いので現状の整理&構築手順の整理をします。
Origamiでは、Redshiftへのデータインポートに関して

  • aggregator(td-agent)からリアルタイムで送っているデータ
  • 毎日のバッチで更新しているデータ

の2種類があります。バッチに関しては実行される時間帯が集約されている事からバッチの実行時間外に再構築を終えてしまえばバッチによる更新データの信頼性は担保できます。

しかし、aggregatorからのデータはリアルタイムでRedshiftに突っ込まれているのでスナップショットを取って新クラスタ復元している間にも旧クラスタのデータは更新されるので信頼性を担保できなくなります。

こういった事情も踏まえて議論した結果、以下の手順で再構築をする事にしました。

クラスタ再構築手順
0. 事前調査
1. Snapshotの作成&Restore(新クラスタの生成)
2. Route53で新クラスタendpointのCNAMEを作成
3. 新クラスタから、リアルタイムで流れる予定のテーブルをtruncateする
4. td-agent.confを再設定してデータの流し先を旧クラスタから新クラスタに変更する
5. 旧クラスタ上の、リアルタイムで流れていたテーブルのデータをS3にUNLOADする
6. S3にあるデータを新クラスタにCOPYする
7. Redshiftへ接続しているサーバーの環境変数を旧クラスタから新クラスタに変更

この手順について1つずつ解説していきます。

事前調査

以下の項目について事前調査を行いました

  • リアルタイムで更新されていくテーブルたち
  • UNLOAD&COPYの動作確認及び要する時間
  • redshiftへ接続しているサーバーたち

Snapshotの作成&Restore

まずは旧クラスタのShapshotを作成し、作成したSnapshotから「Restore From Snapshot」を選択して新クラスタを作成します。
今回は全く同じ仕様のクラスタを作るので cluster identifierVPC security group などの設定は旧クラスタの設定をそのまま当てはめます。

Route53で新クラスタendpointのCNAMEを作成

これは任意ですが、redshiftのendpointが長いのでCNAMEを作成します。
ここではNameを redshift.production としておきます。

(Valueは {cluster_identifier}.hogehoge.{region_name}.redshift.amazonaws.com です。)

新クラスタから、リアルタイムで流れる予定のテーブルをtruncateする

事前にリアルタイムにデータが流れるテーブルを調査し、そのテーブルをtruncateします。またこの記事では便宜上、リアルタイムでデータが流れている対象のテーブルを table_A のみとします。

truncate.sql
truncate table_A; 

-- 確認用(0だったらok)
select count(*) from table_A;

td-agent.confを再設定してデータの流し先を旧クラスタから新クラスタに変更

# CNAMEの確認
$ dig redshift.production

# td-agent.confの変更
$ vim /etc/td-agent/td-agent.conf

今回のケースでは送り先のredshiftのhostとpasswardを変更します。

# td-agent再起動
$ sudo service td-agent restart

# ログの確認
$ sudo tail -f /var/log/td-agent/td-agent.log

再起動した瞬間から、今まで旧クラスタに流れていたデータは新クラスタに流れるようになります。

# 新クラスタの対象のテーブルにcount数があることを確認
production=# select count(*) from table_A;
count
-------
    289(適当です)
(1 row)

旧クラスタ上の、リアルタイムで流れていたテーブルのデータをS3にUNLOADする

事前に任意のバケット及びフォルダ(table_A)をS3に作成しておきます。

旧クラスタにsshで接続して以下を実行

UNLOAD ('select * from table_A')
TO 's3://{作ったバケット}/table_A/'
CREDENTIALS 'aws_access_key_id=hogehoge;aws_secret_access_key=hugahuga';

参考: UNLOADの例(AWS公式)

※データの性質によっては delimiter などのオプションをつける必要があるので、コマンドは事前に調査して用意し、コピペするだけの状態にしておく事をお勧めします。

S3にあるデータを新クラスタにCOPYする

新クラスタにsshで接続して以下を実行

COPY action_logFROM 's3://{作ったバケット}/table_A/'
CREDENTIALS 'aws_access_key_id=hgoehgoe;aws_secret_access_key=hugahuga';

参考: COPY(AWS公式)

確認方法は旧クラスタ及び新クラスタの table_A のレコード数を確認して、
旧クラスタのレコード数 < 新クラスタのレコード数(= 旧クラスタのレコード数 + td-agentの向き先を変更してから流れてきたレコード数) 
となっていれば大丈夫です。
( td-agentの向き先を変更してから流れてきたレコード数 に関しては単位時間あたりにどのくらいレコード数が増えているのかを概算で見積もっておく )

Redshiftへ接続しているサーバーの環境変数を旧から新に変更

ここまで終わるとredshiftのクラスタは完全に入れ替わったので、あとは事前に調査したredshiftに接続している全サーバーの環境変数を旧から新に変更して $ source.bashrc 等しておけば大丈夫です。

そして全ての動作が期待通りに動いている事を確認し、旧クラスタをdeleteしました。

まとめ

リアルタイムデータに関しては上記の手順を踏む事で、データの信頼性が担保された状態で移行できました。

事前に要件を整理して手順をまとめ、開発環境で一度試して動作確認をしておくのが肝だと思っています(当たり前ですが)。本番環境での移行は機械的に淡々と出来るようにしておくのが個人的な理想です。
またこういったオペレーションは稀なので記録をしっかり残しておく事が今後の為になると思います。

続きを読む

Amazon Linux AMIとUbuntuでのTimeZone設定 date PHP Ruby JavaでのTimeZone出力確認

Amazon Linux AMIとUbuntuでのTimeZone設定と設定によってPHP Ruby Javaの出力の違いを確認してみました。

Amazon Linux AMIの初期設定 確認

Amazon Linux AMIの初期設定は/etc/localtime /etc/sysconfig/clock共にUTCになっています。

/etc/localtime /etc/sysconfig/clockの初期設定

$ cat /etc/localtime
TZif2UTCTZif2UTC
UTC0

$ cat /etc/sysconfig/clock 
ZONE="UTC"
UTC=true

date PHP Ruby Javaでの確認

$ date
2016年 xx月 xx日 x曜日 xx:xx:xx UTC

$ php -a
php > echo date_default_timezone_get();
UTC

$ irb
irb(main):001:0> Time.now.zone
=> "UTC"

# Java System.out.println(TimeZone.getDefault());
sun.util.calendar.ZoneInfo[id="UTC",...]

Ubuntuの初期設定 確認

Ubuntuには/etc/sysconfig/clockが存在しない代わりに/etc/timezoneが存在します。

/etc/localtime /etc/timezoneの初期設定

$ cat /etc/localtime
TZif2UTCTZif2UTC
UTC0

$ cat /etc/timezone
UTC

date PHP Ruby Javaでの確認

$ date
xx xx:xx:xx UTC 2016

$ php -a
php > echo date_default_timezone_get();
UTC

$ irb
irb(main):001:0> Time.now.zone
=> "UTC"

# Java System.out.println(TimeZone.getDefault());
sun.util.calendar.ZoneInfo[id="UTC",...]

Amazon Linux AMIとUbuntuのlocaltimeの変更

Amazon Linux AMIとUbuntu共に/usr/share/zoneinfo以下の中から設定したいTimeZoneを選び、/etc/localtimeにリンクを張ります。

TimeZoneをAmerica/Los_Angelesに変更

$ ls /usr/share/zoneinfo
Africa      Chile    Factory    Iceland      MET       posix       UCT
America     CST6CDT  GB     Indian       Mexico    posixrules  Universal
Antarctica  Cuba     GB-Eire    Iran         MST       PRC     US
Arctic      EET      GMT    iso3166.tab  MST7MDT   PST8PDT     UTC
...

$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime

localtimeをAmerica/Los_Angelesに変更後Amazon Linux AMIで確認

/etc/localtimeをAmerica/Los_Angelesに変更後/etc/localtime /etc/sysconfig/clockの確認

$ cat /etc/localtime
...
PST8PDT,M3.2.0,M11.1.0

$ cat /etc/sysconfig/clock 
ZONE="UTC"
UTC=true

date PHP Ruby Javaでの確認

$ date
2016年 xx月 xx日 x曜日 xx:xx:xx PST

$ php -a
php > echo date_default_timezone_get();
America/Los_Angeles

$ irb
irb(main):001:0> Time.now.zone
=> "PST"

# Java System.out.println(TimeZone.getDefault());
sun.util.calendar.ZoneInfo[id="UTC",...]

localtimeをAmerica/Los_Angelesに変更後Ubuntuで確認

/etc/localtimeをAmerica/Los_Angelesに変更後/etc/localtime /etc/timezoneの確認

$ cat /etc/localtime
...
PST8PDT,M3.2.0,M11.1.0

$ cat /etc/timezone
UTC

date PHP Ruby Javaでの確認

$ date
xx xx:xx:xx PST 2016

$ php -a
php > echo date_default_timezone_get();
America/Los_Angeles

$ irb
irb(main):001:0> Time.now.zone
=> "PST"

# Java System.out.println(TimeZone.getDefault());
sun.util.calendar.ZoneInfo[id="UTC",...]

Javaに変更したTimeZoneを適用

JavaのTimeZone出力では/etc/localtimeを変更しただけではTimeZoneが変更されませんでした。
Amazon Linux AMIでは/etc/sysconfig/clock、Ubuntuでは/etc/timezoneも変更します。

Amazon Linux AMIの場合は/etc/sysconfig/clockを変更します。

$ sudo vim /etc/sysconfig/clock 
ZONE="America/Los_Angeles"
UTC=true

# Java System.out.println(TimeZone.getDefault());
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",...]

Ubuntuの場合は/etc/timezoneを変更します。

$ sudo vim /etc/timezone
America/Los_Angeles

# Java System.out.println(TimeZone.getDefault());
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",...]

またUbuntuの場合はdpkg-reconfigure tzdataをすることで/etc/localtime /etc/timezoneの両方を同時に設定もできます。

スクリーンショット 2016-12-22 12.00.28.png

$ dpkg-reconfigure tzdata
# 上記設定画面が表示されAmerica/Los_Angelesを設定

# Java System.out.println(TimeZone.getDefault());
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",...]

結果

TimeZoneを変更する際には/etc/localtimeの他にAmazon Linux AMIでは/etc/sysconfig/clock Ubuntuでは/etc/timezoneを変更する必要がありそうです。

続きを読む

AWS EC2にphpMyAdminをインストールしてRDSのMySQLに接続するメモ

EC2にphpMyAdminをインストールするのに手こずってしまったのでメモ

PHP環境構築

こちらを参考にPHPまでをインストール

phpMyAdminをインストール

SSHでEC2にアクセス

rootユーザーになる

$ sudo -i

ディレクトリを作成→移動

# mkdir -p /var/www/html/tools
# cd /var/www/html/tools

phpMyAdminのパッケージをダウンロード

# wget https://files.phpmyadmin.net/phpMyAdmin/4.6.5.2/phpMyAdmin-4.6.5.2-all-languages.tar.gz

自分の環境の場合https経由でダウンロードができなかったので手近なサーバーに上げ直してダウンロードしました。

解凍してリネーム

# tar xvzf phpMyAdmin-4.6.5.2-all-languages.tar.gz
# mv phpMyAdmin-4.6.5.2-all-languages phpMyAdmin

設定

# cd phpMyAdmin
# cp config.sample.inc.php config.inc.php
# vi config.inc.php

ホストを指定

$cfg['Servers'][$i]['host'] = 'localhost';

$cfg['Servers'][$i]['host'] = 'エンドポイント';

に変更…。
これでhttp://{パブリックIP}/tools/phpMyAdmin/にアクセスで行けるハズ………がいけません。
mbstringがないというエラーが出てログイン画面がでません。

php-mbstringのインストール

# yum install -y php55-mbstring

まだダメ…

こちらの記事でPDOとpdo_mysqlモジュールが入ってないと動かないとのことでインストール

# yum install -y php55-pdo
# yum install -y php-mysqli

php.iniのファイルに2行を追記します

# vim /etc/php.ini
extension=mysqli.so
extension=mbstring.so

最後に再起動

# service httpd restart

これで無事アクセスできました。
phpMyAdmin.png

続きを読む

EC2でjekyllの準備

背景

S3や、heroku等で運用している方が多いみたいですが、EC2をすでに借りていたので同じインスタンスの中に立てる。
けど、1発で起動できなかったのでログがてらまとめます。

jekyllとは

といっても本家を見たほうがいいです。
本家 => https://jekyllrb-ja.github.io

jekyllをEC2にインストール

サイトに書いてあるコマンドだけでは動きませんでした:weary:
rubyの環境セットアップしてなかったら、しょうがない。。。

jekyllインストール

$ sudo yum install -y ruby-devel
$ gem install bundler
$ bundle install

// io-consoleエラーが発生したため、インストール
$ gem install io-console
$ gem install jekyll

$ jekyll new my-poor-site
$ cd my-poor-site
$ vim _config.yml

_config.ymlに下記の1行を追加

下記を入れないと外部から接続できません。
確かにサーバー内でのcurlは通って、ローカルのブラウザからは接続できませんでした。

_config.yml
host: 0.0.0.0

jekyllの起動

$ jekyll s

http://{ec2-host}:4000

ブラウザで上記URLに接続してください。正常に表示されていれば接続できれば起動成功です。

[おまけ]

postファイルにはフォーマットが定義されているらしい。
http://jekyllrb-ja.github.io/docs/posts/

$ cd _post
$ vim YEAR-MONTH-DAY-title.mad

あまり良く読まないで始めるもんだからちょっと悩みました。

続きを読む