AWS Lambda と Pythonを使ってメタデータをAmazon Elasticsearch Serviceにインデクシング

AWS Solutions Architect ブログ: 【AWS Database Blog】AWS Lambda と Pythonを使ってメタデータをAmazon Elasticsearch Serviceにインデクシング. 続きを読む

private docker registry のはまりどころ(S3連携、SSL)

AWSでprivate docker registryをコンテナで立てた際のハマったポイントをメモ。

ケース

・AWS EC2(Amazon Linux)上にコンテナで実現
・コンテナイメージはdocker registry 2.1
・ボリュームはS3に設定
・SSLはオレオレ証明書で対応
・外部インターネットとの接続は原則ない(クローズド環境)

ハマった点

① docker push/pullで以下のようなエラーとなる

x509: certificate signed by unknown authority

opensslでオレオレ証明書(crtファイル)を作成し、pull/pushするクライアント側に格納するが、/etc/docker/certs.d/配下に格納してしまうと、dockerがOSのroot証明書を参照しなくなり、S3など他のSSL接続ができなくなる。
今回のようにボリュームをS3に指定する場合は特に注意が必要。
crtファイルは[registryのドメイン名].crtとして、/etc/pki/ca-trust/source/anchors配下に格納する。

その後、設定を反映し、dockerを再起動する。

sudo update-ca-trust
sudo service restart docker

これでエラーは解決した。

② docker push/pullでio.timeoutとなる

SSLの設定が解決したところで、クライアントからpush/pullをすると、以下のようなエラーとなる。

tcp XX.XX.XX.XX:443: i/o timeout.

IPアドレスを逆引きしてみると、s3-ap-northeast-1.amazonaws.comと判明。

どうやらクライアントは、push/pushの際、docker registryサーバだけでなく、S3に直接イメージpull/pushのための接続を行っている模様。
つまり、docker registryでボリュームをS3に指定した場合、当然registryを立てたEC2から、ボリュームとして指定したS3バケットへのアクセス権(ポリシーやROLEなど)は必須だが、同時にクライアント側も対象S3バケットへアクセスできる必要がある。
オンプレミスとのハイブリッド環境など、クローズドな環境の場合は特に注意が必要。

スクリーンショット 2017-01-18 20.54.28.png

参考

https://ishiis.net/2017/01/12/docker-distribution-install/

http://dev.classmethod.jp/cloud/docker-registry-recipes/

https://docs.docker.com/registry/deploying/#configure-tls-on-a-registry-server

http://qiita.com/suzukihi724/items/c8135fcfbf74fcbc80d0

続きを読む

シンプルな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/

続きを読む

無料で使えるSSL – 「Let’s Encrypt」をh2oに設定してhttp2.0に対応してみた

2014年11月にElectronic Frontier Foundation、Mozilla、Cisco Systems、ミシガン大学などが共同で立ち上げた「Let’s Encrypt」。最近では、GoogleがSSLを推奨しており、サイトをSSL対応しておかないと検索順位に影響すると言われています。そんな中、Let’s Encryptは無料で使えるということもあり、導入しているサイトも増えてきています。個人でSSLを導入してhttp2.0対応するにはちょうど良いですね。

ここでは、「Let’s Encrypt」をインストール&h2oに設定してhttp2.0に対応させる方法を説明します。

インストールに使用した環境

Amazon Linux AMI release 2016.09
h2o 2.1.0

h2oがインストールされており、80番ポート、443番ポートともに通信可能になっていることが前提となります。ファイアウォールで80、443が遮断されているとインストールできませんのでご注意下さい。

インストール手順

ソースコード取得

Let’s Encryptのソースコードをgithubのリポジトリから取得してきます。
以下の例では、ホームディレクトリにcloneしていますが、場所はどこでもかまいません。

cd ~
git clone https://github.com/letsencrypt/letsencrypt

インストール

letsencrypt-autoというコマンドが用意されているのでこれを使用します。
必要なパッケージ類を一通りインストールしてくれます。
rootユーザーで実行する必要がありますので、sudoをつけましょう。

cd ./letsencrypt
sudo ./letsencrypt-auto --debug --help

実行中に
「Installing Python packages…」でしばらく止まります。少し待ちましょう。。
以下のようなメッセージが表示されたらインストール完了です。

Installation succeeded.

  letsencrypt-auto [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...

Certbot can obtain and install HTTPS/TLS/SSL certificates.  By default,
it will attempt to use a webserver both for obtaining and installing the
cert. The most common SUBCOMMANDS and flags are:

obtain, install, and renew certificates:
    (default) run   Obtain & install a cert in your current webserver
    certonly        Obtain or renew a cert, but do not install it
    renew           Renew all previously obtained certs that are near expiry
   -d DOMAINS       Comma-separated list of domains to obtain a cert for

  --apache          Use the Apache plugin for authentication & installation
  --standalone      Run a standalone webserver for authentication
  --nginx           Use the Nginx plugin for authentication & installation
  --webroot         Place files in a server's webroot folder for authentication
  --manual          Obtain certs interactively, or using shell script hooks

   -n               Run non-interactively
  --test-cert       Obtain a test cert from a staging server
  --dry-run         Test "renew" or "certonly" without saving any certs to disk

manage certificates:
    certificates    Display information about certs you have from Certbot
    revoke          Revoke a certificate (supply --cert-path)
    delete          Delete a certificate

manage your account with Let's Encrypt:
    register        Create a Let's Encrypt ACME account
  --agree-tos       Agree to the ACME server's Subscriber Agreement
   -m EMAIL         Email address for important account notifications

More detailed help:

  -h, --help [TOPIC]    print this message, or detailed help on a topic;
                        the available TOPICS are:

   all, automation, commands, paths, security, testing, or any of the
   subcommands or plugins (certonly, renew, install, register, nginx,
   apache, standalone, webroot, etc.)

証明書発行

次に、letsencrypt-autoコマンドを使って証明書の発行を行います。
パラメータに、h2o.confで設定されているホームディレクトリ、ドメイン名(FQDN)、管理者のメールアドレスを指定します。

sudo ./letsencrypt-auto certonly _
  --webroot -w [h2oホームディレクトリのパス] _
  -d [ドメイン名(FQDN)] _
  -m [管理者のメールアドレス] _
  --agree-tos

コマンドを実行すると、以下のディレクトリに証明書とキーファイルが作成されます。

/etc/letsencrypt/live/[ドメイン名]/fullchain.pem
/etc/letsencrypt/live/[ドメイン名]/privkey.pem

h2o.confの設定

作成された証明書をh2o.confに設定します。
併せてhttpでアクセスがあった場合に自動的にhttpsに301リダイレクトする設定にします。

pid-file: /etc/h2o/pid-file
user: root
access-log: /var/log/h2o/access-log
error-log: /var/log/h2o/error-log
file.index: [ 'index.php', 'index.html' ]

listen: 80
listen:
  port: 443
  ssl:
        # 作成した証明書ファイルを設定
    certificate-file: /etc/letsencrypt/live/[ドメイン名]/fullchain.pem
    key-file:  /etc/letsencrypt/live/[ドメイン名]/privkey.pem

hosts:
    # httpでアクセスがあった場合、httpsに301リダイレクトする
  "[ドメイン名]:80":
    paths:
      "/":
        redirect:
          url: https://[ドメイン名]/
          status: 301

    # httpsでアクセスがあった場合はホームディレクトリへ
  "[ドメイン名]:443":
    paths:
      "/":
        file.dir: [ホームディレクトリ]

h2o.confのテスト

設定ファイルに問題が無いかチェックします。

sudo service h2o configtest

証明書を設定してconfigtestを行うと、証明書ファイルのチェックもしてくれます。
結果の中に以下が表示されていれば、正しく証明書が発行されています。

... 略

/etc/letsencrypt/live/[ドメイン名]/fullchain.pem: good

... 略

/etc/letsencrypt/live/[ドメイン名]/fullchain.pem: good

... 略

configuration OK
                                                           [  OK  ]

h2o再起動

configtestに成功したらh2oを再起動しましょう。

sudo service h2o restart

動作確認

ブラウザからhttpsでアクセスして動作を確認します。
以下のように「保護された通信」と緑色で表示されればOKです。

スクリーンショット 2017-01-15 23.13.59.png

また、httpでアクセスすると、自動的にhttpsにリダイレクトされるかもチェックしておきましょう。

併せてアクセスログを見て、http2.0通信になっているか確認します。

XXX.XXX.XXX.XXX - - [15/Jan/2017:23:19:48 +0900] "GET / HTTP/2" 200 43306 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"

http2.0で通信されていれば、アクセスログに「HTTP/2」と表示されます。

まとめ

Let’s Encryptを使うことで、コストをかけること無くSSL通信に対応することが出来ます。また、h2o等のサーバに設定することによってhttp2.0にも対応することが出来るのでとても便利です。是非一度お試し下さい。

最後に注意点

Let’s Encryptは他の証明書とは異なり、3ヶ月に1回更新が必要となります。
無料で使える代わりに管理者の所在確認をまめに行う必要があると言ったところでしょうか。。

しかし3ヶ月に1回更新する手間を考えても、無料で使えるというのは大きなメリットだと思います。

続きを読む

新元号までのカウントダウンページを作ってみた(S3 + CloudFront + Route53 + ACM:SSL対応)

2019年1月1日より新元号になる、との報道がありました。
新元号は平成31年元日から 皇室会議を経て閣議決定へ 法案提出は今年5月連休明け

平成元年生まれの私としては、非常にインパクトのあるニュースです。
そこで、名残惜しい平成との別れを見守るため、新元号までのカウントダウンページを作ってみました。

平成カウントダウン
https://heisei.today/

最近では静的ページでもSSL化することが一般化しつつあるので、
勉強がてらSSL対応のページをAWSをフル活用して作成してみました。
(AWSの各サービスを初めて利用したので、設定に誤り等あればご指摘ください。)

今回は S3 + CloudFront + Route53 + ACM(AWS Cetificate Manager) を利用しています。

手順としては

  • S3に静的サイトを用意する
  • 独自ドメインで公開する
  • SSL対応させる
  • リダイレクト設定しておく

です。

S3に静的サイトを用意する

まずはこちらに記載されている手順に従って、静的サイトを作成していきます。
独自ドメインを使用して静的ウェブサイトをセットアップする – Amazon Simple Storage Service

SSL対応させる

静的サイトが用意できれば、今度はSSL対応化していきます。
ACM(AWS Cetificate Manager)でSSL証明書を発行し、それをCloudFrontを利用して紐付けます。
その後、Route53でS3のバケットを参照していたものを、CloudFrontを参照するよう変更します。
この手順に関しては、こちらの記事に詳細が書かれているので、この通りに進めます。
AWS Certificate Manager 無料のサーバ証明書でCloudFrontをHTTPS化してみた

これにて準備完了。

それではSSL化したページ( https://heisei.today )にアクセスしてみましょう。

678340605_Screen Shot 2015-10-09 at 9.32.26 PM.png
むむ、Access Denied になってしまっています。
どうやらまだ設定することがあるようです。

CloudFrontの設定変更

S3バケットに対して、CloudFrontからのアクセスのみ受け付けるように設定していきます。

[CloudFront + S3]特定バケットに特定ディストリビューションのみからアクセスできるよう設定する

作成したCloudFrontのディストリビューションの設定を変更します。
Restric Bucket Access が No になっていたので、こちらを Yes に変更します。

ss 2017-01-14 19.21.39.png

設定の反映には最大15分ほど時間がかかるようです。
反映されているかどうか、S3のバケットポリシーを確認します。

ss 2017-01-14 18.30.22.png

CloudFrontのオリジンアクセスアイデンティティ設定が付与されていれば設定完了です。

ついでに、リダイレクト設定もしておきます。

HTTPでアクセスされた時に、HTTPSにリダイレクトするよう設定変更します。
CloudFrontのディストリビューションを開き、Behaviosrのタブを選択します。
そして、Viewer Protocol PolicyRedirect HTTP to HTTPSにして保存すれば完了です。

ss 2017-01-14 20.05.27.png

これで http://heisei.today にアクセスした時に、https://heisei.today にリダイレクトされていればOKです。

ちなみに、今回の S3 + CloudFront + Route53 という構成は、AWSにおける静的コンテンツ配信パターンカタログ(アンチパターン含む)で最高の構成であると紹介されています。
アクセスが急増しても、cloudFrontを挟んでいるため、高いパフォーマンスが発揮でき、オリジンに負荷をかけることが無いから、だそうです。

おわりに

新元号の発表が、2019年1月1日の当日であったり半年前であったりと、さまざまな情報が飛び交っているようなので、エンジニアとしては、新元号の行く末は継続して見守る必要がありますね。
新元号は2019年1月1日の当日発表!→SEたちの悲痛な叫びが集っている「年明けからシステム屋さんが死ぬ未来しか見えない」 – Togetter
19年元日に新天皇即位、元号は半年前までに – 読売新聞

こちらの記事を参考にさせていただきました。

続きを読む

sample build

contents


変数の設定:

  export AWS_DEFAULT_REGION='us-east-1'

変数の設定:

  IAM_ROLE_ARN=$( 
    aws iam get-role 
      --role-name ${IAM_ROLE_NAME} 
      --query 'Role.Arn' 
      --output text 
  ) 
    && echo ${IAM_ROLE_ARN}

Step 1: S3バケットの作成

変数の設定:

  AWS_ID=$( 
    aws sts get-caller-identity 
      --query 'Account' 
      --output text 
  ) && echo ${AWS_ID}

アーティファクト保存用バケットの作成

変数の設定:

  S3_BUCKET_ARTIF="artifact-${AWS_ID}-handson-$(date +%Y%m%d)" 
    && echo ${S3_BUCKET_ARTIF}

変数の確認:

  cat << ETX

    S3_BUCKET_ARTIF:    ${S3_BUCKET_ARTIF}
    AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION}

  ETX

コマンド:

  aws s3api create-bucket 
    --bucket ${S3_BUCKET_ARTIF}

コマンド:

  aws s3api get-bucket-location 
    --bucket ${S3_BUCKET_ARTIF}

ソースコード保存用バケットの作成

変数の設定:

  S3_BUCKET_NAME="src-${AWS_ID}-handson-$(date +%Y%m%d)" 
    && echo ${S3_BUCKET_NAME}

変数の確認:

  cat << ETX

    S3_BUCKET_NAME:     ${S3_BUCKET_NAME}
    AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION}

  ETX

コマンド:

  aws s3api create-bucket 
    --bucket ${S3_BUCKET_NAME}

コマンド:

  aws s3api get-bucket-location 
    --bucket ${S3_BUCKET_NAME}

Step 2: Create the Source Code to Build

コマンド:

  mkdir -p codebuild-demo/src/{main,test}/java 
    && cd codebuild-demo

コマンド:

  FILE_INPUT='src/main/java/MessageUtil.java'

コマンド:

  cat << EOF > ${FILE_INPUT}
  public class MessageUtil {
    private String message;

    public MessageUtil(String message) {
      this.message = message;
    }

    public String printMessage() {
      System.out.println(message);
      return message;
    }

    public String salutationMessage() {
      message = "Hi!" + message;
      System.out.println(message);
      return message;
    }
  }
  EOF

  cat ${FILE_INPUT}

コマンド:

  FILE_INPUT='src/test/java/TestMessageUtil.java'

コマンド:

  cat << EOF > ${FILE_INPUT}
  import org.junit.Test;
  import org.junit.Ignore;
  import static org.junit.Assert.assertEquals;

  public class TestMessageUtil {

    String message = "Robert";
    MessageUtil messageUtil = new MessageUtil(message);

    @Test
    public void testPrintMessage() {
      System.out.println("Inside testPrintMessage()");
      assertEquals(message,messageUtil.printMessage());
    }

    @Test
    public void testSalutationMessage() {
      System.out.println("Inside testSalutationMessage()");
      message = "Hi!" + "Robert";
      assertEquals(message,messageUtil.salutationMessage());
    }
  }
  EOF

  cat ${FILE_INPUT}

コマンド:

  FILE_INPUT='pom.xml'

コマンド:

  cat << EOF > ${FILE_INPUT}
  <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>messageUtil</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>Message Utility Java Sample App</name>
    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
      </dependency>
    </dependencies>
  </project>
  EOF

  cat ${FILE_INPUT}

Step 3: Create the Build Spec

コマンド:

  FILE_INPUT='buildspec.yml'

コマンド:

  cat << EOF > ${FILE_INPUT}
  version: 0.1

  phases:
    install:
      commands:
        - echo Nothing to do in the install phase...
    pre_build:
      commands:
        - echo Nothing to do in the pre_build phase...
    build:
      commands:
        - echo Build started on `date`
        - mvn install
    post_build:
      commands:
        - echo Build completed on `date`
  artifacts:
    files:
      - target/messageUtil-1.0.jar
  EOF

  cat ${FILE_INPUT}

Step 4: Add the Source Code and the Build Spec to the Input Bucket

コマンド:

  FILE_ZIP="${HOME}/MessageUtil.zip"

コマンド:

  zip -r ${FILE_ZIP} *

コマンド:

  aws s3 cp ${FILE_ZIP} s3://${S3_BUCKET_NAME}/

Step 5: Create the Build Project

変数の設定:

  FILE_INPUT='project.json'

変数の設定:

  CODEB_PROJECT_NAME='sample-project'

コマンド:

  cat << EOF > ${FILE_INPUT}
  {
    "name": "${CODEB_PROJECT_NAME}",
    "source": {
      "type": "S3",
      "location": "${S3_BUCKET_NAME}/MessageUtil.zip"
    },
    "artifacts": {
      "type": "S3",
      "location": "${S3_BUCKET_ARTIF}"
    },
    "environment": {
      "type": "LINUX_CONTAINER",
      "image": "aws/codebuild/java:openjdk-8",
      "computeType": "BUILD_GENERAL1_SMALL"
    },
    "serviceRole": "${IAM_ROLE_ARN}"
  }
  EOF

  cat ${FILE_INPUT}

変数の設定:

  jsonlint -q ${FILE_INPUT}

コマンド:

  aws codebuild create-project 
    --cli-input-json file://${FILE_INPUT}

結果(例):

  {
    "project": {
      "name": "sample-project",
      "serviceRole": "arn:aws:iam::XXXXXXXXXXXX:role/CodeBuildServiceRole",
      "created": 1484302441.508,
      "artifacts": {
          "namespaceType": "NONE",
          "packaging": "NONE",
          "type": "S3",
          "location": "artifact-XXXXXXXXXXXX-handson-20170113",
          "name": "sample-project"
      },
      "lastModified": 1484302441.508,
      "timeoutInMinutes": 60,
      "environment": {
          "computeType": "BUILD_GENERAL1_SMALL",
          "image": "aws/codebuild/java:openjdk-8",
          "type": "LINUX_CONTAINER",
          "environmentVariables": []
      },
      "source": {
          "type": "S3",
          "location": "src-XXXXXXXXXXXX-handson-20170113/MessageUtil.zip"
      },
      "encryptionKey": "arn:aws:kms:us-east-1:XXXXXXXXXXXX:alias/aws/s3",
      "arn": "arn:aws:codebuild:us-east-1:XXXXXXXXXXXX:project/sample-project"
    }
  }

Step 6: Run the Build

コマンド:

  aws codebuild start-build 
    --project-name ${CODEB_PROJECT_NAME}

結果(例):

  {
    "build": {
      "buildComplete": false,
      "initiator": "AdministratorforAssumeRole/AdministratorforAssumeRole-session",
      "artifacts": {
          "location": "arn:aws:s3:::artifact-XXXXXXXXXXXX-handson-20170113/sample-project"
      },
      "projectName": "sample-project",
      "timeoutInMinutes": 60,
      "buildStatus": "IN_PROGRESS",
      "environment": {
          "computeType": "BUILD_GENERAL1_SMALL",
          "image": "aws/codebuild/java:openjdk-8",
          "type": "LINUX_CONTAINER",
          "environmentVariables": []
      },
      "source": {
          "type": "S3",
          "location": "src-XXXXXXXXXXXX-handson-20170113/MessageUtil.zip"
      },
      "currentPhase": "SUBMITTED",
      "startTime": 1484302576.313,
      "id": "sample-project:6f362776-d66f-4364-aeb3-28d7ce9ceafa",
      "arn": "arn:aws:codebuild:us-east-1:XXXXXXXXXXXX:build/sample-project:6f362776-d66f-4364-aeb3-28d7ce9ceafa"
    }
  }

コマンド:

  aws codebuild list-builds-for-project 
    --project-name ${CODEB_PROJECT_NAME}

結果(例):

  {
    "ids": [
      "sample-project:6f362776-d66f-4364-aeb3-28d7ce9ceafa"
    ]
  }

コマンド:

  ARRAY_CODEB_BUILD_IDS=$( 
    aws codebuild list-builds-for-project 
      --project-name ${CODEB_PROJECT_NAME} 
      --query 'ids' 
      --output text 
  ) 
    && echo ${ARRAY_CODEB_BUILD_IDS}

Step 7: View Summarized Build Information

コマンド:

  aws codebuild batch-get-builds 
    --ids ${ARRAY_CODEB_BUILD_IDS}

結果(例):

  {
    "buildsNotFound": [],
    "builds": [
      {
          "buildComplete": true,
          "phases": [
              {
                  "phaseStatus": "SUCCEEDED",
                  "endTime": 1484302576.608,
                  "phaseType": "SUBMITTED",
                  "durationInSeconds": 0,
                  "startTime": 1484302576.313
              },
              {
                  "contexts": [
                      {
                          "message": "Service role arn:aws:iam::XXXXXXXXXXXX:role/CodeBuildServiceRole does not allow AWS CodeBuild to create Amazon CloudWatch Logs log streams for build arn:aws:codebuild:us-east-1:XXXXXXXXXXXX:build/sample-project:6f362776-d66f-4364-aeb3-28d7ce9ceafa. Error message: User: arn:aws:sts::XXXXXXXXXXXX:assumed-role/CodeBuildServiceRole/AWSCodeBuild is not authorized to perform: logs:CreateLogStream on resource: arn:aws:logs:us-east-1:XXXXXXXXXXXX:log-group:/aws/codebuild/sample-project:log-stream:6f362776-d66f-4364-aeb3-28d7ce9ceafa. Service role arn:aws:iam::XXXXXXXXXXXX:role/CodeBuildServiceRole does not allow AWS CodeBuild to create Amazon CloudWatch Logs log streams for build arn:aws:codebuild:us-east-1:XXXXXXXXXXXX:build/sample-project:6f362776-d66f-4364-aeb3-28d7ce9ceafa. Error message: User: arn:aws:sts::XXXXXXXXXXXX:assumed-role/CodeBuildServiceRole/AWSCodeBuild is not authorized to perform: logs:CreateLogStream on resource: arn:aws:logs:us-east-1:XXXXXXXXXXXX:log-group:/aws/codebuild/sample-project:log-stream:6f362776-d66f-4364-aeb3-28d7ce9ceafa",
                          "statusCode": "ACCESS_DENIED"
                      }
                  ],
                  "phaseType": "PROVISIONING",
                  "phaseStatus": "CLIENT_ERROR",
                  "durationInSeconds": 0,
                  "startTime": 1484302576.608,
                  "endTime": 1484302577.535
              },
              {
                  "phaseType": "COMPLETED",
                  "startTime": 1484302577.535
              }
          ],
          "artifacts": {
              "location": "arn:aws:s3:::artifact-XXXXXXXXXXXX-handson-20170113/sample-project"
          },
          "projectName": "sample-project",
          "timeoutInMinutes": 60,
          "initiator": "AdministratorforAssumeRole/AdministratorforAssumeRole-session",
          "buildStatus": "FAILED",
          "environment": {
              "computeType": "BUILD_GENERAL1_SMALL",
              "image": "aws/codebuild/java:openjdk-8",
              "type": "LINUX_CONTAINER",
              "environmentVariables": []
          },
          "source": {
              "type": "S3",
              "location": "src-XXXXXXXXXXXX-handson-20170113/MessageUtil.zip"
          },
          "currentPhase": "COMPLETED",
          "startTime": 1484302576.313,
          "endTime": 1484302577.535,
          "id": "sample-project:6f362776-d66f-4364-aeb3-28d7ce9ceafa",
          "arn": "arn:aws:codebuild:us-east-1:XXXXXXXXXXXX:build/sample-project:6f362776-d66f-4364-aeb3-28d7ce9ceafa"
      }
    ]
  }

Step 8: View Detailed Build Information

(仕様変更になった?)

Step 9: Get the Build Output Artifact

コマンド:

  aws s3 ls s3://${S3_BUCKET_ARTIF} --recursive

Clean Up

続きを読む

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

前提条件

IAMへの権限

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

AWS CLIのバージョン

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

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

結果(例):

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

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

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

0. 準備

プロファイルの確認

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

コマンド
aws configure list

結果(例)

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

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

変数の設定:

     export AWS_DEFAULT_PROFILE=<IAMユーザ名>

1. 事前作業

1.1. IAMロール名の決定

変数の設定
IAM_ROLE_NAME='CodeBuildServiceRole'

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

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

結果(例):

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

1.2. プリンシパルの決定

変数の設定
IAM_PRINCIPAL='codebuild.amazonaws.com'

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

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

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

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

cat ${FILE_INPUT}

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

コマンド
jsonlint -q ${FILE_INPUT}

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

2. 本作業

IAMロールの作成

変数の確認
cat << ETX

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

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

結果(例):

3. 事後作業

IAMロールの確認

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

結果(例):

完了

続きを読む

CloudFrontでSSLv3プロトコルを許可する

ATS対応の一環でCloutFrontをSSLv3に対応させる作業があったためメモ。
結局あまりに駆け込みが多かったのか、「やっぱ延期で、いつまで延期するかは未定」ってオチになりましたが。

念のため大前提として書いておくのですが、
これは一旦やり過ごすだけの対応であり、将来的にはちゃんとアプリ側でSSLv3を使わずに済むよう対応する必要があります。

必要なもの

  • 正規のSSL証明書(自己署名証明書、通称オレオレ証明書だとNG)
  • 月あたり600USD
  • 証明書をアップロードしたいAWSアカウントに対してawsコマンドが叩ける環境

証明書のアップロード

aws iam upload-server-certificate --server-certificate-name [識別名] --certificate-body [証明書ファイル] --private-key [秘密鍵ファイル] --certificate-chain [中間証明書ファイル] --path /cloudfront/

識別名:AWSのコンソールから見える証明書の名前
証明書ファイル:証明書のパス
秘密鍵ファイル:秘密鍵のパス、パスワード付きはNG
中間証明書ファイル:中間証明書のパス

※各ファイルのパスは先頭に「file://」を付け、コマンドを実行したディレクトリから見たパスで記述すること

証明書の適用

CloudFrontにて、以下のように設定を変更します。
image

ちなみに、ここで正規の証明書ではなく自己署名証明書を上げていた場合、CloudFrontでこんなエラーが出て設定を変更することができません。

com.amazonaws.services.cloudfront.model.InvalidViewerCertificateException: The specified SSL certificate doesn't exist, isn't valid, or doesn't include a valid certificate chain. (Service: AmazonCloudFront; Status Code: 400; Error Code: InvalidViewerCertificate; Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

要約すると「無効な証明書だから使わせねーよ」ってトコでしょうか。
そりゃそうです。

続きを読む