Raspberry Pi 3 でAlexaと対話する

re:Invent 2017AlexaをRaspberryPi経由で利用する方法のワークショップに参加した。

ワークショップでは、構成済みのMicroSDを使って、単純にコマンドを流したり、支持されたようにファイルを書き換えたりしただけだったので、まっさらなMicroSDの状態から同様に動くようになるまでをやってみたいと思う。

用意するもの

Raspberry Pi 3 Model B
USBマイク < 多分これ
イヤホン
USBキーボード
USBマウス
HDMIケーブル
ディスプレイ
MicroSD Class10 16G

事前準備(Macでの作業)

インストーラーのダウンロード

こちらからNOOBSをダウンロードする
今回ダウンロードしたバージョンは v2.4.5

MicroSDにコピーする

ダウンロードしたNOOBS_v2_4_5.zipを解凍しFAT32でフォーマット済みのMicroSDにファイルをコピーする。

コマンド例
cp -a Downloads/NOOBS_v2_4_5/* /Volumes/UNTITLED

Raspbianのインストール

先ほどのMicroSDをRaspberry Piに刺して電源を入れるとインストーラーが立ち上がるので、Raspbian [RECOMMENDED]にチェックを入れて、左上のInstallをクリックしてインストールすればOK



事前準備(Raspberry Piでの準備)

一応OSのバージョンチェック

こんな記載があるので、Raspbian Stretchかどうか一応チェック

This guide provides step-by-step instructions to set up the Alexa Voice Service (AVS) Device SDK on a Raspberry Pi running Raspbian Stretch with Desktop

$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)"
NAME="Raspbian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

ネットワークに接続

有線LANもあるので有線でつなげる場合には、何もしなくて大丈夫
Wi-Fiを利用する場合には右上にマークがあるのでそこから接続設定する

SSH/VNCの有効化

有効化しなくてもOKだけど、リモートから行える作業はできるだけリモートで行うと楽なので有効化しておく


ビルド環境の構築+ビルド

基本的にはこちらにある通りに行う
https://github.com/alexa/avs-device-sdk

依存関係のあるライブラリ、AVS Device SDK, Sensory wake word engine のインストール

Sensory wake word engineはオープンソースは非商用限定のライセンスなので注意すること

cd /home/pi/
mkdir sdk-folder
cd sdk-folder
mkdir sdk-build sdk-source third-party application-necessities
cd application-necessities
mkdir sound-files

sudo apt-get update

sudo apt-get -y install 
  git gcc cmake build-essential libsqlite3-dev libcurl4-openssl-dev 
  libfaad-dev libsoup2.4-dev libgcrypt20-dev libgstreamer-plugins-bad1.0-dev 
  gstreamer1.0-plugins-good libasound2-dev doxygen

cd /home/pi/sdk-folder/third-party
wget -c http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz
tar zxf pa_stable_v190600_20161030.tgz
cd portaudio
./configure --without-jack
make

pip install commentjson

cd /home/pi/sdk-folder/sdk-source
git clone git://github.com/alexa/avs-device-sdk.git

cd /home/pi/sdk-folder/third-party
git clone git://github.com/Sensory/alexa-rpi.git
cd ./alexa-rpi/bin/
./license.sh

ビルド

make のオプションは -j4までいけるけど、オーバーヒートに注意しましょう(と書いてある)

cd /home/pi/sdk-folder/sdk-build
cmake /home/pi/sdk-folder/sdk-source/avs-device-sdk 
  -DSENSORY_KEY_WORD_DETECTOR=ON 
  -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH=/home/pi/sdk-folder/third-party/alexa-rpi/lib/libsnsr.a 
  -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR=/home/pi/sdk-folder/third-party/alexa-rpi/include 
  -DGSTREAMER_MEDIA_PLAYER=ON 
  -DPORTAUDIO=ON 
  -DPORTAUDIO_LIB_PATH=/home/pi/sdk-folder/third-party/portaudio/lib/.libs/libportaudio.a 
  -DPORTAUDIO_INCLUDE_DIR=/home/pi/sdk-folder/third-party/portaudio/include
make SampleApp -j2

Alexa Voice Serviceへの登録

先ほどビルドしたSampleAppからAlexa Voice Serviceへ接続するためには設定を作る必要があるので作成する。

Amazon Developerに登録

こちらからログインする
日本でもEchoが発売されたのでamazon.co.jpアカウントでもログインできるらしい

Alexa Voice Service に製品を登録する

!!! 一度作成した設定は削除できないようなので注意 !!!

Alexa Voice Service を選ぶ

CREATE PRODUCTを選ぶ

プロダクト情報を入力する

Product Name: 任意
Product ID: 任意(あとで使うのでメモっておくこと)
Is your product an app or device?: Device
Will your device use —–: 任意(Deviceを選ぶと出てくる)
Product category: 任意
Brief product description: 任意
How will end users —–: Hands-free
Upload an image: 任意
Do you intend to —–: No
Is this a children’s —–: No

セキュリティプロファイルの設定を作る

CREATE NEW PROFILEを選択すると、下にプロファイル名と説明を入れる欄が出るので、入力する。

URLの登録

次で使うので、Client ID, Client Secretをメモに取る
http://localhost:3000を入力してADDボタンを押す
同様にAllowed return URLsにはhttp://localhost:3000/authresponseを入力してADDボタンを押す

アプリケーション側に設定を反映

その前にvimのインストール

nano, edとかは入ってるけど辛いのでvimをインストール

sudo apt-get install -y vim

設定ファイルの書き換え

YOUR_CLIENT_SECRET, YOUR_CLIENT_ID, YOUR_PRODUCT_IDを先ほどメモったものに書き換えて以下を実行する

cat <<EOF >/home/pi/sdk-folder/sdk-build/Integration/AlexaClientSDKConfig.json
{
    "authDelegate":{
        "clientSecret":"YOUR_CLIENT_SECRET",
        "deviceSerialNumber":"123456",
        "refreshToken":"",
        "clientId":"YOUR_CLIENT_ID",
        "productId":"YOUR_PRODUCT_ID"
   },
   "alertsCapabilityAgent":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/alertsCapabilityAgent.db"
   },
   "settings":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/settings.db",
        "defaultAVSClientSettings":{
            "locale":"en-US"
        }
   },
   "certifiedSender":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/certifiedSender.db"
   },
   "sampleApp":{
       "displayCardsSupported":false
   }
}
EOF

音の設定

cat <<EOF >~/.asoundrc
pcm.!default {
  type asym
   playback.pcm {
     type plug
     slave.pcm "hw:0,0"
   }
   capture.pcm {
     type plug
     slave.pcm "hw:1,0"
   }
}
EOF

リフレッシュトークンの取得

先ほど書き換えず空のままだったrefreshTokenを取得する

まずは、下記コマンドを実行して認証用Webサーバーを立ち上げる

cd /home/pi/sdk-folder/sdk-build && python AuthServer/AuthServer.py

直接もしくはVNCを利用して、Raspberry Pi内のブラウザを立ち上げて http://localhost:3000/ にアクセスするとSign in to [セキュリティプロファイル名] using your Amazon accountのページにリダイレクトされるので、ログインする

ログインが完了すると立ち上げてWebサーバーにリダイレクトされて戻ってきて、The file is written successfully.
Server is shutting down, so you can close this window.
という表示がされている

サンプルアプリケーションの実行

$ cd /home/pi/sdk-folder/sdk-build/SampleApp/src
$ TZ=UTC ./SampleApp 
  /home/pi/sdk-folder/sdk-build/Integration/AlexaClientSDKConfig.json 
  /home/pi/sdk-folder/third-party/alexa-rpi/models
#############################
#       Connecting...       #
#############################

########################################
#       Alexa is currently idle!       #
########################################

///ここに大量のエラーが出ているけど、一旦動作には関係なさそうなので放置///

                  #    #     #  #####      #####  ######  #    #
                 # #   #     # #     #    #     # #     # #   #
                #   #  #     # #          #       #     # #  #
               #     # #     #  #####      #####  #     # ###
               #######  #   #        #          # #     # #  #
               #     #   # #   #     #    #     # #     # #   #
               #     #    #     #####      #####  ######  #    #

       #####                                           #
      #     #   ##   #    # #####  #      ######      # #   #####  #####
      #        #  #  ##  ## #    # #      #          #   #  #    # #    #
       #####  #    # # ## # #    # #      #####     #     # #    # #    #
            # ###### #    # #####  #      #         ####### #####  #####
      #     # #    # #    # #      #      #         #     # #      #
       #####  #    # #    # #      ###### ######    #     # #      #

+----------------------------------------------------------------------------+
|                                  Options:                                  |
| Wake word:                                                                 |
|       Simply say Alexa and begin your query.                               |
| Tap to talk:                                                               |
|       Press 't' and Enter followed by your query (no need for the 'Alexa').|
| Hold to talk:                                                              |
|       Press 'h' followed by Enter to simulate holding a button.            |
|       Then say your query (no need for the 'Alexa').                       |
|       Press 'h' followed by Enter to simulate releasing a button.          |
| Stop an interaction:                                                       |
|       Press 's' and Enter to stop an ongoing interaction.                  |
| Privacy mode (microphone off):                                             |
|       Press 'm' and Enter to turn on and off the microphone.               |
| Playback Controls:                                                         |
|       Press '1' for a 'PLAY' button press.                                 |
|       Press '2' for a 'PAUSE' button press.                                |
|       Press '3' for a 'NEXT' button press.                                 |
|       Press '4' for a 'PREVIOUS' button press.                             |
| Settings:                                                                  |
|       Press 'c' followed by Enter at any time to see the settings screen.  |
| Speaker Control:                                                           |
|       Press 'p' followed by Enter at any time to adjust speaker settings.  |
| Info:                                                                      |
|       Press 'i' followed by Enter at any time to see the help screen.      |
| Quit:                                                                      |
|       Press 'q' followed by Enter at any time to quit the application.     |
+----------------------------------------------------------------------------+

こんな感じで動いてとりあえずひと段落、Alexaと呼びかけるか、t + EnterでLinteningモードに入る
そうすると、以下のように、ステータスが遷移して、Speakingで話してくれるはず、、、はず、、、

############################
#       Listening...       #
############################

###########################
#       Thinking...       #
###########################

###########################
#       Speaking...       #
###########################

########################################
#       Alexa is currently idle!       #
########################################

あれれ?

音が出ない

音が出ない何故???と思ったら、ディスプレイ側のピンジャックにイヤホンを繋げたら声が聞こえる
ということはHDMI側に信号が流れてしまっているみ???

インターフェイスの優先度的な問題かと思い、HDMIを抜いたり、刺したり、設定変えたりしたけれども解決の糸口が見えないのでとっても困ってます

例えば、以下を実行してもやっぱりHDMI経由で音が出力される・・・

$ amixer cset numid=3 1
numid=3,iface=MIXER,name='PCM Playback Route'
  ; type=INTEGER,access=rw------,values=1,min=0,max=2,step=0
  : values=0

上の状況でYouTubeを確認したところ、ピンジャックからの出力になっていたので、ここの設定はAlexaには関係なさそう
また、ワークショップで使ったMicroSDを刺して起動した場合には、しっかりとピンジャックから音が出力されるので何らかの設定ミスの線が濃厚

RaspberryPiの知見も、Linuxで音を出す的な知見も全くないので、お手上げ状態です
どなたかわかる方いらっしゃったら教えてもらえたら嬉しいです

音が切れる

もう一つの問題として、Alexaの話が途中で切れることWhat's your nameときくとMy name is Aleという感じで途中で切れてしまう
これも設定で何とかできるんじゃないかと思っているけれども、今の所、どう調べたらいいものやらという感じで止まっている

音がピンジャックから出ない問題と同様に教えてもらえたら嬉しいです

助けてもらえるときのために、関連しそうな情報を末尾に載せておきます

今後のやりたいこと

単純にAlexaと声をかけて、声で操作するだけならEchoを使えばいいだけなので、すぐに思いつくようなことだけではあるけれども、以下のようなことを考えている

  • Wake word engineが独立しているので、Alexa以外の言葉に反応させられたら、専用で作る意味が出てくるので、試したい
  • Wake wordだけでなくコマンドから入力受付状態にできるので、受付システムとかできそうなので試したい

音の設定関連の情報

$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
$ aplay -L
null
    Discard all samples (playback) or generate zero samples (capture)
default
sysdefault:CARD=ALSA
    bcm2835 ALSA, bcm2835 ALSA
    Default Audio Device
dmix:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Direct sample mixing device
dmix:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Direct sample mixing device
dsnoop:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Direct sample snooping device
dsnoop:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Direct sample snooping device
hw:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Direct hardware device without any conversions
hw:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Direct hardware device without any conversions
plughw:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Hardware device with all software conversions
plughw:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Hardware device with all software conversions
$ amixer cset numid=3
numid=3,iface=MIXER,name='PCM Playback Route'
  ; type=INTEGER,access=rw------,values=1,min=0,max=2,step=0
  : values=0

続きを読む

Windows 10 ローカルに AWS S3 のクローン(minio)を手軽につくる

2017/12/10

Dockerのほうが動かなかったのでWSL側で動かしてみたらすぐにできたのでメモ。
(windowsでkitematic上で手軽に終えたかったのですが)

WSL: Windows Subsystem for Linux

(minioにたどり着くまでに、fakes3とs3ninjaをdockerで使ってみましたが、aws-sdkでput時にエラーが出たので乗り換えました)

minio のインストールと起動まで

以下からLinux向けをダウンロード
https://github.com/minio/minio

  • rootユーザーでやりましたが、rootである必要はないかも・・・
cd /root
wget https://dl.minio.io/server/minio/release/linux-amd64/minio

chmod +x minio
mkdir /minio_data

./minio server /root/minio_data

動作すると下記のような表示が出ます。

Created minio configuration file successfully at /root/.minio
Endpoint: ~省略~ http://127.0.0.1:9000
AccessKey: ~省略~
SecretKey: ~省略~

Browser Access: ~省略~ http://127.0.0.1:9000

Command-line Access: ~省略~ 

Object API (Amazon S3 compatible):
   Go:         https://docs.minio.io/docs/golang-client-quickstart-guide
   Java:       https://docs.minio.io/docs/java-client-quickstart-guide
   Python:     https://docs.minio.io/docs/python-client-quickstart-guide
   JavaScript: https://docs.minio.io/docs/javascript-client-quickstart-guide
   .NET:       https://docs.minio.io/docs/dotnet-client-quickstart-guide

Drive Capacity: 532 GiB Free, 910 GiB Total

minio の UI を使ってみる

  • ブラウザで http://127.0.0.1:9000 にアクセス

image.png

  • 上記のメッセージに出てきたアクセスキーとシークレットキーを入力してログイン

image.png

  • バケットを作る (入力してenter)

image.png

  • バケットを消す(消す機能はUIにない?)

仕方なく・・・ rm -rf で削除

# ll /root/minio_data/
合計 0
drwxrwxrwx 0 root root 4096 12月 11 01:17 ./
drwx------ 0 root root 4096 12月 11 00:50 ../
drwxrwxrwx 0 root root 4096 12月 11 00:57 .minio.sys/
drwxrwxrwx 0 root root 4096 12月 11 01:16 sample/
drwxrwxrwx 0 root root 4096 12月 11 01:17 sample2/
rm -rf /root/minio_data/sample2

ちゃんと消えました

spring boot から minio にアクセスしてみたときのポイント

接続情報としては下記でOKでした(SDKバージョンなどで書き方は少し違いがあるかもしれません)

エンドポイント:http://127.0.0.1:9000
アクセスキーとシークレットキーは上記のもの

ClientConfiguration clientConfig = new ClientConfiguration();
clientConfig.setProtocol(Protocol.HTTP); <- ここがポイント!

EndpointConfiguration endpointConfiguration = new EndpointConfiguration("http://127.0.0.1:9000", null); <- ここがポイント!

AWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY); <- ここがポイント!
AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);

AmazonS3 client = AmazonS3ClientBuilder.standard()
                            .withCredentials(credentialsProvider)
                            .withClientConfiguration(clientConfig)
                            .withEndpointConfiguration(endpointConfiguration).build();

今日はここまで m(_ _)m

続きを読む

AmazonLinux に Zabbix3.0 をインストールした話

構成

Zabbix Server : EC2
Database : RDS(MariaDB)

EC2やRDSの構築は飛ばします

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

EC2にSSHでログインして、以下。

PHP & Apache

yum -y install httpd24 mysql php70 php70-devel php70-gd php70-mbstring php70-mcrypt php70-pdo php70-pecl-apcu-devel zlib-devel php70-mysqlnd php70-bcmath

mariadb

RDSを使うので不要

PHP の設定

vim /etc/php.ini

リポジトリのインストール

rpm -ivh http://repo.zabbix.com/zabbix/3.0/rhel/6/x86_64/zabbix-release-3.0-1.el6.noarch.rpm

AmazonLinux は init 制御なので RHEL6 版をインストールします。
systemd に変わったら RHEL7 版に変更。

Zabbix3.0 のインストール

yum install zabbix-server-mysql.x86_64 zabbix-web-mysql.noarch zabbix-web-japanese.noarch zabbix-get zabbix-agent

設定

cp /usr/share/doc/zabbix-web-3.0.9/httpd24-example.conf /etc/httpd/conf.d/zabbix.conf
vim /etc/httpd/conf.d/zabbix.conf

以下を変更します。

  • 読み込むモジュールの変更

    変更前:<IfModule mod_php5.c>
    変更後:<IfModule mod_php7.c>

  • タイムゾーンの設定

    変更前:#php_value date.timezone Europe/Riga
    変更後:php_value date.timezone Asia/Tokyo

続きを読む

自動Blue-Greenデプロイをansibleで構築してみた

自己紹介

一年目のインフラエンジニア
クラウドなんだからBlue-Greenデプロイをやりたいと言われ、構築して運用してみた。

Blue-Greenデプロイの構築方法

Blue-Greenデプロイの概要

AWSで環境を構築するにあたり以下の図のように設計してみました。
この図の通り、ALBにてルーティングしているターゲットグループに紐づいているEC2を切り替えることで、Blue Greenデプロイメントを行っています。
AWSのホワイトペーパー(p. 32)にもあったのですが、DNSのルーティングを行う方法に比べ、Rollbackにおけるリスクを減らすことができます。

デプロイのために作成したplaybook

実際にansibleでplaybookを組んでみました。
なんかrolesがたくさんあるのは、ひとつひとつのrolesをシンプルにしたかったからです。
なぜなら、rolesが複雑になるとあとで管理が面倒になる、と先輩に言われたからだったり、、、
(今はとても実感しています。本当に分けた方がいい。。。)

---
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prd"}

playbookの解説

  • EC2立ち上げ

まずはEC2を立ち上げるrolesを作成、ここは後々他のデプロイ機能でも活用できるように切り出してみました。セキュリティ的な意味で、userはrootではなく別にユーザーを作成。

provision.yml
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2

※EC2を立ち上げてRoute53に登録、SSH接続の確認まで行うrolesを例として載せておきます。変数は別のplaybookで指定しています。(気になるところあればコメントください。)

roles/ec2/tasks/main.yml
- name: Provision EC2 Box
  local_action:
    module: ec2
    key_name: "{{ ec2_keypair }}"
    group_id: "{{ ec2_security_group }}"
    instance_type: "{{ ec2_instance_type }}"
    image: "{{ ec2_image }}"
    vpc_subnet_id: "{{ ec2_subnet_ids[(inventory_hostname_short[-1:] | int % 2 )] }}"
    region: "{{ ec2_region }}"
    assign_public_ip: no
    instance_profile_name: "{{ ec2_iam_role | default }}"
    wait: true
    user_data: "{{ lookup('template', 'roles/ec2/templates/user_data_hostname.yml.j2') }}"(※hostnameを動的に付けたかったのでtemplateを作成しusr_dataを使用)
    exact_count: 1
    count_tag:
      Name: "{{ inventory_hostname }}"
    instance_tags:
      Name: "{{ inventory_hostname }}"
      Environment: "{{ ec2_tag_Environment }}"
      Service: "{{ service_name }}"
    volumes:
    - device_name: /dev/xvda
      device_type: gp2
      volume_size: "{{ ec2_volume_size }}"
      delete_on_termination: true
  register: ec2

- debug: var=item
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Setup for DNS in AWS Route 53
  route53:
    aws_access_key:
    aws_secret_key:
    command: create
    private_zone: True
    zone: bdash.inside
    record: '{{ inventory_hostname }}.bdash.inside'
    type: A
    ttl: 60
    value: '{{ item.private_ip }}'
    overwrite: yes
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Wait for the instances to boot by checking the ssh port
  wait_for:
    host: '{{ item.private_ip }}'
    port: 22
    delay: 60
    timeout: 320
    state: started
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode
  • ミドルウェアからデプロイまで

webアプリはrailsで組まれているので、よくある組み合わせのnginx、unicornで構築しました。
DBはインフラで管理するので、database.ymlを作成して配置するrolesを別途作成しています。
あとは、デプロイしただけでmigrationまで行ってあげる親切設計。

ちなみにrelease-testのrolesは疎通確認、ヘルスチェックを行うrolesです。
もしうまくデプロイからnginx,unicornの起動までできなかった場合はansibleが落ちます。
これでデプロイしたけど、見てみたら動いていないなんてことはない。

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
  • デプロイ後

デプロイ後にターゲットグループの切り替えを行います。
ここでBlue-Greenデプロイを実現しています。ターゲットグループに登録して、削除するまで行いました。

ただ、このrolesは本番だけは行わず、手動で切り替えています。なぜなら、削除を行ってしまうと問題が起きた時にロールバックしにくくなる、と言うのが一番の理由です。うまくやればできるとは思うので、残論点です。

開発環境はそんなの関係ないので、利便性を取っています。

    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prduction"}

いざ、運用!!

ここまでで無事、Blue-Greenデプロイが実現できました。
当初の予定通り、ターゲットグループが自動で切り替わり、外部からの接続も確認できました。
そして、ansibleで構成管理をしているためサーバーを作っては壊すこともできます。
つまり、Immutable Infrastructureも実現できており、データを作っては壊す開発環境にはもってこいなものができました。

追加で実装に組み込んだテストrolesによって、「ansibleが通ったけれどサービスが動いていない!」なんてぬか喜びもなくすことができました。

まさに、いいことずくめ!!
自動デプロイにひとつ近づいた!

と思っていたのですが、ここで更なる問題が、、、

切り替え後にterminate処理をいれたら、アプリのdaemonなどのプロセスが強制KILLされる。

たとえばアプリ側でデータ出力や取込に数時間かかるという話はよくある話です。
そんな時にターゲットグループ切り替えだけならいざしらず、
サーバーを落としてしまったのならプロセスが強制的にKILLされ、結果処理停止となります。

自動デプロイを実装した結果、アプリ側に影響を出してしまいました。
この辺りはBlue-Greenデプロイを行う上で考慮すべき項目でしたね。

このことからプロセス監視を行うことが、今後の課題かなと思っています。
AWSにはスポットインスタンスもあるので、terminateしても動き続けられるようアプリ側にサルベージできる機能か何かも欲しいですね。

マイクロサービスだったことを忘れていた

運用に載せてからしばらくたつと、サービスも増え立てるサーバーも増えてきます。
つまり、、、デプロイの回数が単純に増えていきます。
更に自動化を進めないと、工数が増加の一途をたどることに・・・

最後に

自動デプロイ、Blue-Greenデプロイをansibleで実装してみました。
サービスのリリーススピードはデプロイの手間に依存する」が私の持論です。
なので、デプロイ作業をいかに自動化しつつ安定化させるかが肝だと思っています。

まだまだ改善できるところは多いので、今週末もansibleと一緒に過ごします~

続きを読む

AWS Batchで速く/安くやるデータセットの前処理

OpenStreamアドベントカレンダーの一日目です。

結構前からやっている趣味DeepLearningですが、最近(実際は結構前から)次のような問題に当たり始めました。

  • データセットが大きくなってきてHDDが厳しい
  • データセットが大きくなってきて前処理がやばい

小さいデータセット+Augmentationでなんとかなるものはいいんですが、現在最大のデータセットは 画像33万枚、220GB弱 あります。
んで、これを前処理したり何だりしていると、最終的に学習で利用するデータを作成するだけで、HDDが500GBくらい利用されてしまう状態です。

容量も当然厳しいんですが、一番厳しいのは処理時間です。現状の前処理を行うと、大体 12時間くらい かかります。趣味でやるので基本的に自分のPCでやっていると、HDDが悲鳴を上げる上に、実行している間はレイテンシが悪すぎて他の作業もできないって状態になってしまっていました。

前処理の中でGPUを利用しているので、GPUを使うような作業も出来ないという。

一部のデータセットで試して〜ってやれば出来ないこともないんですが、結局最後には全部やらないとならないので、この機会に AWS Batch を使って、一気に処理出来ないかどうかを試してみました。

Azure Batchとかもありますが、とりあえずはAWSで。Azure Batchでも同じようなことは出来るかと
GCPのDataflowのだとApache Beamに縛られるので、今回は対象外としました

前提

今回作成するジョブは大きく2つです。

  • 画像のリサイズ
  • エッジの抽出

また、今回は前処理だけ出来ればいいので、GPUインスタンスは使いません。

後述するように、基本的に AWS CloudFormation を利用します。基本的にAWS CLIは構築時点では利用しません。

では行ってみましょう。

AWS Batchについて

日本(の特にSIer的な人々)にとって、Batchと言えばJP1とかああいったフロー制御を想像してしまいますが、それとは異なります。

こちら でわかりやすくまとめられています。Batchって言われると脊髄反射的にGUIが・・・とか思ってしまうのはなんとかしたいです。

上記の記事で説明はされているので、ここではAWS Batchが何かということについては記述しません。

ジョブ実行用コンテナの作成

まずはジョブ実行用のDocker imageを作成します。今回はサイズにこだわって、次のような構成にしてみました。

  • Alpine Linux 3.6
  • Python 3.6.3
  • OpenCV 3.2.0

で、実際に利用しているDockerfileがこんな感じになります。

FROM alpine:3.6

ENV OPENCV_VERSION 3.2.0
ENV PYTHON_VERSION 3.6.3

RUN apk add --update --no-cache 
    build-base 
    openblas 
    openblas-dev 
    unzip 
    curl 
    cmake 
    libjpeg 
    libjpeg-turbo-dev 
    libpng-dev 
    jasper-dev 
    tiff-dev 
    libwebp-dev 
    clang-dev 
    openssl 
    ncurses 
    libstdc++ 
    linux-headers && 
    mkdir -p /opt && 
    apk add --no-cache --virtual .build-python 
      openssl-dev 
      ncurses-dev 
      xz && 
    curl -LO https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tar.xz && 
    unxz Python-${PYTHON_VERSION}.tar.xz && 
    tar xf Python-${PYTHON_VERSION}.tar -C /opt && 
    cd /opt/Python-${PYTHON_VERSION} && 
    ./configure --enable-shared && make -j > /dev/null && make install > /dev/null && 
    cd / && 
    rm -rf /opt/Python-${PYTHON_VERSION} Python-${PYTHON_VERSION}.tar && 
    pip3 install numpy==1.13.3 && 
    apk del --virtual .build-python && 
    curl -LO https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.zip && 
    unzip ${OPENCV_VERSION}.zip -d /opt && 
    rm -rf ${OPENCV_VERSION}.zip && 
    mkdir -p /opt/opencv-${OPENCV_VERSION}/build && 
    cd /opt/opencv-${OPENCV_VERSION}/build && 
    cmake 
      -D CMAKE_BUILD_TYPE=RELEASE 
      -D CMAKE_INSTALL_PREFIX=/usr/local 
      -D WITH_FFMPEG=NO 
      -D WITH_IPP=NO 
      -D WITH_OPENEXR=NO 
      -D WITH_TBB=YES 
      -D BUILD_TESTS=NO 
      -D BUILD_EXAMPLES=NO 
      -D BUILD_ANDROID_EXAMPLES=NO 
      -D INSTALL_PYTHON_EXAMPLES=NO 
      -D BUILD_DOCS=NO 
      -D BUILD_opencv_python2=NO 
      -D BUILD_opencv_python3=ON 
      -D PYTHON3_EXECUTABLE=/usr/local/bin/python3 
      -D PYTHON3_INCLUDE_DIR=/usr/local/include/python3.6m/ 
      -D PYTHON3_LIBRARY=/usr/local/lib/libpython3.so 
      -D PYTHON_LIBRARY=/usr/local/lib/libpython3.so 
      -D PYTHON3_PACKAGES_PATH=/usr/local/lib/python3.6/site-packages/ 
      -D PYTHON3_NUMPY_INCLUDE_DIRS=/usr/local/lib/python3.6/site-packages/numpy/core/include/ 
      .. && 
    make VERBOSE=1 && 
    make -j2 && 
    make install && 
    cd / && 
    rm -rf /opt && 
    apk del gcc build-base openblas-dev clang-dev linux-headers cmake unzip

全部一つのRUNに押し込めているので、失敗すると悲惨ですが、これで作成したimageは 273MB 程度と、それなりに扱いやすいサイズになります。

このimageは、実際に作業をさせるimageのBase imageになるので、小さいにこしたことはないです。

実際に利用するImage

実際に利用するImageは、以下で管理しています。

https://github.com/derui/painter-tensorflow/tree/master/batch/image-converter

単純に必要なPythonスクリプトをコピーして、pipで依存のインストールをしているだけです。

AWS Batchの環境作成

AWS Batchは、Managed環境とUnmanaged環境の二通りが選べますが、基本的にはManagedでいいかと思います。しかし、ManagedでもJobを動かすまでには以下のようなリソースが必要になります。

AWS Batchが様々なサービスを組み合わせて構築されている証左ですが、手動で作ってると色々とめんどくさいです。

  • VPC/Subnet

    • ECSが使われるので、PublicかPrivate+NAT Gatewayがセットアップされてないと厳しいです
  • SecurityGroup
  • ECSのService Role
    • InstanceProfileも必要です
  • AWS BatchのService Role
  • JobのRole
    • ECSのInstanceProfileとは別で必要です
  • ECR
    • Publicなimageでよければ、Dockerhubとかでもいいんですが、大抵はECRが必要かと

ということで、CloudFormationのテンプレートを作りました。

https://github.com/derui/painter-tensorflow/tree/master/batch/cfn.yml

ただ、必要なリソースを全部入れているので、結構量の多いテンプレートになっています。そこで、Batch系のリソースだけ抜粋します。

  ComputeEnvironment:
    Type: AWS::Batch::ComputeEnvironment
    Properties:
      Type: MANAGED
      ServiceRole: !Sub "arn:aws:iam::${AWS::AccountId}:role/AWSBatchServiceRole"
      ComputeEnvironmentName: C4OnDemand
      ComputeResources:
        MaxvCpus: 128
        SecurityGroupIds:
          - !Ref InstanceSecurityGroup
        Type: EC2
        Subnets:
          - !Ref PrivateSubnet
        MinvCpus: 0
        ImageId: ami-a1b2c3d4
        InstanceRole: ecsInstanceRole
        InstanceTypes:
          - c4.large
          - c4.xlarge
          - c4.2xlarge
          - c4.4xlarge
          - c4.8xlarge
        Ec2KeyPair: batch-compute
        Tags: {"Name": "Batch Instance - C4OnDemand"}
        DesiredvCpus: 48
      State: ENABLED

  MyRepository: 
    Type: "AWS::ECR::Repository"
    Properties: 
      RepositoryName: "image-convert"
      RepositoryPolicyText:
        Version: "2012-10-17"
        Statement: 
          - 
            Sid: AllowPushPull
            Effect: Allow
            Principal:
              AWS: !Sub "arn:aws:iam::${AWS::AccountId}:user/${User}"
            Action: 
              - "ecr:GetDownloadUrlForLayer"
              - "ecr:BatchGetImage"
              - "ecr:BatchCheckLayerAvailability"
              - "ecr:PutImage"
              - "ecr:InitiateLayerUpload"
              - "ecr:UploadLayerPart"
              - "ecr:CompleteLayerUpload"
          - 
            Sid: AllowPull
            Effect: Allow
            Principal:
              AWS: !GetAtt JobRole.Arn
            Action: 
              - "ecr:GetDownloadUrlForLayer"
              - "ecr:BatchGetImage"
              - "ecr:BatchCheckLayerAvailability"

  JobDefinitionForResize:
    Type: 'AWS::Batch::JobDefinition'
    Properties:
      Type: container
      JobDefinitionName: !Sub
          - ${Service}-resize-image
          - { Service: !Ref ServiceName}
      Parameters:
        Bucket: !Ref Bucket
        ExcludeFiles: exclude.txt
        Size: 128
      ContainerProperties:
        Command:
          - python3
          - -m
          - resize_fix_size
          - -b
          - "Ref::Bucket"
          - -d
          - resized
          - -e
          - "Ref::ExcludeFiles"
          - -s
          - "Ref::Size"
          - --crop
          - "Ref::Prefix"
        Memory: 1000
        JobRoleArn: !Ref JobRole
        Vcpus: 1
        Image: !Sub
          - "${AWS::AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${Repository}/image-converter"
          - {"Repository": !Ref MyRepository}
      RetryStrategy:
        Attempts: 1

  JobQueue:
    Type: AWS::Batch::JobQueue
    Properties:
      ComputeEnvironmentOrder:
        - Order: 1
          ComputeEnvironment: !Ref ComputeEnvironment
      State: ENABLED
      Priority: 1
      JobQueueName: HighPriority

これでも全体の1/3くらいです。AWS Batchは、Compute Environmentでリソースの総量を管理し、Job Queueで流量を制御し、Job Definitionで流れるJobの内容を制御します。

実はCloudFormationのドキュメントにある AWS::Batch::JobDefinition のサンプルには罠があり、RetryStrategyが無いため、真似するともれなくエラーになります。(記述時点:2017/12)

大抵コピーで済ます人(自分)は間違いなく踏む地雷かと。

AWS BatchのServiceRoleについて

AWS BatchのServiceRoleは、恐らくCLI/CloudFormationで作成する人だけが引っかかる地雷があります。ドキュメントのTroubleShootingにわざわざ書いてあるくらいなので、よくあったんでしょう・・・私も見事に踏みました。

単純に言うとARNの指定間違いなのですが、これをやってしまうと1時間程度消すことも消す準備をすることもできず、もれなく待ち状態になります。

Important
Do not attempt to delete a compute environment that is in an INVALID state due to a misconfigured AWS Batch service role. This could cause your environment to get stuck in a DELETING state for up to an hour, and you cannot update the compute environment until the operation times out and fails back to INVALID.
http://docs.aws.amazon.com/batch/latest/userguide/troubleshooting.html#invalid_service_role_arn から引用

Management ConsoleからRoleを作成していて、それをそのまま利用していれば基本的には起こりません。AWS Batchを手動で作るのめんどいなーって人や自動化バンザイって人はご注意を。

Jobを実行する

今回対象にしているテータセットは、全部sha256 hashを名前にしています。それを利用して、prefixをJobのパラメータに渡すようにしてみました。

実際にprefixの分け方=並列度で、どれくらい実行時間が変わるかを試してみました。試すのは画像のリサイズ処理です。

ちなみにローカルでは、8並列で平均して100枚/5秒です。無視するデータを抜いた25万枚だと250分=4時間くらいかかります。

  • prefixを0000〜ffffにした時(65535個のJob)

    • 1jobあたり平均して5から6枚を処理
    • 途中で断念
    • Jobのsubmitだけで4時間くらいかかる(4job/秒)
    • Jobあたりの枚数が少なすぎて、起動→終了のオーバーヘッドの方が大きい
  • prefixを000〜fffにした時(4096個のJob)
    • 1jobあたり平均して80枚を処理
    • 実行時間は40分ほど
    • 25万枚/40分=100枚/秒
    • vCPUの利用率は大体15〜20%くらい
    • Single threadだったので、Networkが明らかにボトルネックになっている
    • 金額はだいたい以下の通り

      c4.8xlarge * 3 = 0.6 * 3 = $1.8/h
      c4.4xlarge * 2 = 0.18 * 2 = $0.36/h
      $2.16/h * 45/60 = $1.54
  • prefixを00〜ffにした時(256個のJob)
    • 1jobあたり平均して1300枚を処理
    • 25万枚/7分=600枚/秒
    • 実行時間は7分ちょっと(!)
    • ただし、各コンテナで8threadで処理するように
    • vCPUの利用率は劇的に改善。大体70〜80%くらい
    • ネットワークの利用度合いが明らかによくなっている
    • 金額はだいたい以下の通り

      c4.8xlarge * 3 = 0.6 * 3 = $1.8/h
      c4.4xlarge * 2 = 0.18 * 2 = $0.36/h
      $2.16/h * 7/60 = $0.24

S3→Instance→S3という経路を辿るため、Networkがボトルネックになります。そのため、1vCPUしかわたしていないコンテナでも、threadを利用したほうがいいと判断した所、大当たりでした。

まさか一回あたり30円くらいまで下げられるとは思いませんでした。

この辺はバッチコンピューティングの経験が問われそうです・・・

依存関係のあるJobを実行する

実際には、リサイズ以外にも処理が必要になります。そこで、こんなスクリプトを作ってみました。

# submit-all-jobs.sh
#!/bin/bash
JOB_QUEUE=xxxxxx

RESIZE_JOB_ARN=$(aws batch describe-job-definitions --job-definition-name image-converter-resize-image --status ACTIVE | jq -r '.jobDefinitions | max_by(.revision).jobDefinitionArn')
EDGE_JOB_ARN=$(aws batch describe-job-definitions --job-definition-name image-converter-extract-edge --status ACTIVE | jq -r '.jobDefinitions | max_by(.revision).jobDefinitionArn')

MAKE_SEQ=$(cat <<EOF
for i in range(0, 0xff):
    print("{:0>2x}".format(i + 1))
EOF
        )

SEQ=$(python -c "$MAKE_SEQ")
echo "$SEQ" | xargs -P 8 -I {} -n 1 
                    ./submit-jobs.sh $JOB_QUEUE $RESIZE_JOB_ARN $EDGE_JOB_ARN {}

# submit-jobs.sh
#!/bin/bash
JOB_QUEUE=$1
RESIZE_JOB_ARN=$2
EDGE_JOB_ARN=$3
SEQ=$4

resize_job=$(aws batch submit-job --job-name "resize-$SEQ-$now" --job-queue $JOB_QUEUE 
    --job-definition $RESIZE_JOB_ARN 
    --parameters Prefix=full/$SEQ
          )

resize_job_id=$(jq '.jobId' "$resize_job")

aws batch submit-job --job-name "edge-$SEQ-$now" --job-queue $JOB_QUEUE 
    --job-definition $RESIZE_JOB_ARN 
    --parameters Prefix=resized/$SEQ 
    --depends-on jobId=$resize_job_id

xargsでお手軽にparallel実行するためのスクリプトです。8並列で行うと、256×2のジョブ登録に数分といったとこ
ろです。

AWS Batchでの依存関係は、submit-jobの depends-on パラメータで指定します。実際にはListなので、複数のJobが終わってから実行する、みたいなことも出来ます。

ここでは、リサイズが終わってからエッジ抽出をしたいので、そういうふうに指定しています。

なお、リサイズ+エッジ抽出を全部やっても 10分 くらいで終わります。今までの苦労は何だったんや・・・。

前処理の要求は止まらない

30万枚の画像が30円くらいでリサイズ出来るようになり、前処理が捗るようになりました。とりあえず現状は概ね満足しています。もっと前処理が必要となってもなんとかなりそうって感覚を得られましたし。

ただ、まだ色々と課題はあります。

  • TFRecordへの変換がめんどくさい

    • 現状はローカルにダウンロードしてきて変換スクリプトかましてます
    • TFRecordにするまでが前処理なので、そのへんが難しいところです
  • 前処理で別のNNを利用する場合どうするか
    • GPUを使うと10倍以上速いので利用したいところですが、個人でやるレベルかって・・・

TFRecordまで一連のJobで出来ると学習が捗るんですが・・・。できそうなので、後で試してみようかと思います。

AWS Batchを今回初めてまともに触ってみましたが、なんとなく既存の仕組みの延長にある感じなので、理解はしやすいように思いました。
EC2が秒単位課金になったこともあり、一気にInstanceを立ち上げてさっさと終わらせるってことができるようになったのも大きいですね。

EMRを使うほどじゃないとか、EMRだと使いにくい言語で大量のデータ処理をしないといけないってなった時には検討してみてはどうでしょうか。

(番外)AWS Batchの注意点

今回試してみて、いくつか?ってなったりハマった点がありました。いずれも2017/12時点です。

  • DashBoardのJob Queueには1000以上は表示できない

    • 1000を超えると 100+ って表示になります。1000超えてるのに何故100?ってなることうけあいです
  • Jobの一覧で検索できない
    • 失敗したJobが100を超えると悲惨です
  • Jobの一覧で並べ替えが出来ない
    • Started atで並べ替えしたい・・・
  • まれにJobがRUNNINGでStuckする
    • TerminateもCancelも効かず、最終的にはJob Queueを削除しました
    • 多分30分くらいすると解消される感じです
  • vCPU/Memoryのバランスに注意
    • ECSと一緒ですね
  • Instanceのスケールダウンが結構早い
    • 5分とかjobが投入されないと全部消す勢いです

GAになってからまだ一年経っていないので、これからもっと使いやすくなっていくんじゃないかと思います。

明日は @pegass85 さんです。

続きを読む

CentOS 7でAmazon EBSのボリュームを拡張する

Amazon EC2インスタンスとしてCentOS 7を選んだ場合にAmazon Elastic Block Store(EBS)のボリュームを拡張する方法です。

本稿では以下の手順に従ってその方法を説明します。

  1. Amazon EBSボリュームを拡張する
  2. ブロックデバイス(/dev/xvda1)のサイズを拡張する

確認した動作環境

  • OS(EC2インスタンス): CentOS Linux release 7.3.1611 (Core)

前提条件

ファイルシステムは CentOS 7標準の「XFS」 であることを前提とします。

ファイルシステムの種類は以下のコマンドを入力することで確認することができます。

$ df -Th
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/xvda1     xfs        16G   16G   0G  99% /
...
...
...

dfコマンド

df コマンドは、システムのディスク領域の使用量についての詳しいレポートを表示します。

18.4. ブロックデバイスとファイルシステムの表示 – Red Hat Customer Portal

-Tはファイルシステムの種類を表示するオプションです。

Amazon EBSボリュームを拡張する

Amazon EBSボリュームの料金体系を確認する

Amazon EBSボリュームを拡張した場合、どれくらいの費用がかかるのかを事前に確認しておきましょう。

リージョンが「アジアパシフィック(東京)」の場合(2017年11月27日現在)

ボリューム 料金(ドル) 料金(円) 単位
Amazon EBS 汎用 SSD (gp2) ボリューム $0.12 12.6円(105円/ドルとした場合) 1 か月にプロビジョニングされたストレージ 1 GB あたり

「Amazon EBS 汎用 SSD (gp2) ボリューム」の場合、I/Oはボリュームの料金に含まれているため、 プロビジョニングした各ストレージのGBに対してのみ課金されます。

最新の料金体系は「料金 – Amazon Elastic Block Store(ブロックストレージ)|AWS」から確認することができます。

「Amazon EC2 コンソール」からEBSのボリュームサイズを変更する

コンソールからEBSのボリュームサイズを変更する手順は
コンソールからの EBS ボリュームの変更 – Amazon Elastic Compute Cloud」から確認することができます。

これまでの手順でEBSのボリュームサイズを変更することができますが、続いてブロックデバイスのサイズを拡張する必要があります。

ブロックデバイス(/dev/xvda1)のサイズを拡張する

ブロックデバイスのサイズを確認する

以下のコマンドを入力してブロックデバイスのサイズを確認しましょう。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  32G  0 disk
└─xvda1 202:1    0  16G  0 part /

上記の例ではxvda1のサイズが16Gであることが分かります。

lsblkコマンド

lsblk コマンドを使用すると、利用可能なブロックデバイスの一覧を表示できます。

一覧表示された各ブロックデバイスについて lsblk コマンドが表示するのは次のとおりです。デバイス名 (NAME)、メジャーおよびマイナーデバイス番号 (MAJ:MIN)、リムーバブルデバイスかどうか (RM)、そのサイズ (SIZE)、読み取り専用デバイスかどうか (RO)、そのタイプ (TYPE)、デバイスのマウント先 (MOUNTPOINT) です。

18.4. ブロックデバイスとファイルシステムの表示 – Red Hat Customer Portal

ブロックデバイスを拡張する

それでは以下のコマンドを入力してブロックデバイスを拡張しましょう。

$ sudo xfs_growfs /dev/xvda1

xfs_growfsコマンド

xfs_growfs コマンドを使用すると、マウント中の XFS ファイルシステムを拡大することができます。

-D size オプションでファイルシステムを指定の size (ファイルシステムのブロック数) まで大きくします。 -D size オプションを指定しない場合、xfs_growfs はデバイスで対応できる最大サイズにファイルシステムを拡大します。

6.4. XFS ファイルシステムのサイズの拡大 – Red Hat Customer Portal

もう一度以下のコマンドを入力してブロックデバイスのサイズを確認しましょう。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  32G  0 disk
└─xvda1 202:1    0  32G  0 part /

xvda1のサイズが16Gから32GBに変更されたことが分かります。


クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。

続きを読む

Packer初歩的な利用方法

00. はじめに

HashiCorpさんの『Packer』
コチラの初歩的な使い方を整理してみました。
Packer_logo.png

00-1. 検証環境

・PackerはMulti Platformに対応しており、それが特徴でもありますが、今回はAWS上での確認のみになります。

■検証環境

Packer v1.1.1
Amazon Linux AMI release 2017.09

01. Packerとは

01-1. 特徴

・公式ドキュメントに簡潔に記載されています。
https://www.packer.io/intro/index.html

上記説明をもとに私の理解は下記になります。

1) 各IaaS・仮想化ソフトウェア/コンテナ管理ソフトウェア(※)にて、カスタムしたマシンイメージを作成できるツール
※AWS、Azure、Google Cloud、VMware、VirtualBoxなど多数のプラットフォームに対応

2) 一度の実行で複数の環境へマシンイメージを作成することが可能
例: AWSでAMIを作成、 並行してAzureでイメージを作成する 等

3) マシンイメージのプロビジョニングをコード管理できる
「Infrastructure as Code」の一種

01-2. Use Case

・ユースケースに関しても、公式に簡潔な説明があります。
https://www.packer.io/intro/use-cases.html

・主な用途としてはベースとなるイメージを要件に沿ってプロビジョニング(サーバセットアップ、コードデプロイ等)した上、カスタムイメージを作成することが挙げられるかと思います。

※個人的な話ですが、AMIを作成するツールと聞いていたため、触るまでは単なるバックアップツールの一種と思っていました。しかし、上述の特徴およびユースケースからも分かるように起動中のEC2からAMIを作成する機能は無いようです(バックアップツールではないのでした)。
→ AWSでしか確認していないため、他platformでも同様のことが言えるか確認できておりませんことご了承ください。

01-3. 考慮すべき事項

・例えば、AutoScalingの起動用AMIを継続的に更新したい場合
プロビジョニングのため該当AMIより一旦インスタンスを起動する必要があるため、同一サーバが重複起動することになります。その際の懸念点(例: S3 や Cloudwatch Logsへの自動ログアップデート処理など)を考慮しておく必要がありそうです。具体的にはそういったサービスは実稼働のlaunch時のみ行うようにする等が考えられるかと思います。

02. 実装例の前に

02-1. 導入はかんたん

・Packerの導入方法はとても簡単ですので、公式の説明を参照下さい。
https://www.packer.io/intro/getting-started/install.html

・また各種コマンドオプションもとてもシンプルになっています。
https://www.packer.io/docs/commands/build.html
https://www.packer.io/docs/commands/validate.html
→ 同HashiCorpさんの『Terraform』同様、構文チェックがあるのはありがたいです。

02-2. Tips

■ 日付付与

1) 例えば「ami_name」をランダムにすべく、日付を付与する場合

{ "ami_name": "Packer {{isotime | clean_ami_name}}" }

↓この場合、下記のようなAMI名となります。
「Packer 2016-07-18T13-22-19Z」
https://www.packer.io/docs/templates/engine.html

2) 文字列_YYYYMMDD-HHMM としたい場合は下記のように指定

{"文字列_{{isotime "20060102-0304"}}" }

↓ 下記のような出力結果に
「文字列_20171120-2008」

※format指定時の”(ダブルコーテーション)はエスケープする必要がありました。
※年は西暦後半2桁しか出力されないため、上二桁を足しています。
TimezoneはUTC固定。この方法ではJSTなどに変更することは出来ないとのことです。

▽format Referece

a date Numeric
Year 06
Month 01(or Jan)
Date 02
Hour 03(or 15)
Minute 04
Second 05

03. 実装例

03-1. 基本的なセットアップがされているAMIの作成

■ 概要

・amzn-linuxのAMIを基に、システム関連の設定、ログインユーザの作成など
サーバ構築時によく行われるセットアップを施したAMIを作成してみようと思います。

■ 詳細

1) 公式配布のAmazon Linuxをベースに
2) yum update実施 (amzn-linuxは配布時点で最新になっているので、この場合不要ですが)
3) TimeZoneやlocaleを日本に
4) 第三者のログイン用ユーザ「guest」を作成。公開鍵も配置
5) swap領域も確保

03-2. 事前準備

■ 03-2-a. ファイル

・packer実行サーバ上に該当ユーザの鍵ペアを作成しておきました。

/home/guest/.ssh/authorized_keys
/home/guest/.ssh/guest.pem

■ 03-2-0. シェルスクリプト

・補足
PackerのTemplate(JSON形式)に直接記載することも可能ですが、
JSONの書式規定により正規表現の利用が難しいため、外部シェルスクリプトを呼び出すというシンプルな形をとってみました。

03-2-1. general.sh

・全般的なものを定義。今回はYum updateのみ

#!/bin/sh

sudo yum -y update

注意: 起動したインスタンスがインターネットに出られる環境であること
※起動時にアサインする VPC(NAT、IGW)、Subnet、SecurityGroupに注意
一時的に起動する場所のため本番と同じNW環境でなくとも問題ありません

03-2-2. system_setup.sh

・TimeZoneなどシステム関連の設定

#!/bin/sh

sudo sed -i -e 's/ZONE="UTC"/ZONE="Asia/Tokyo"/g' /etc/sysconfig/clock
sudo cp  -f /usr/share/zoneinfo/Japan /etc/localtime
sudo sed -i -e 's/repo_upgrade: security/repo_upgrade: none/g' /etc/cloud/cloud.cfg
sudo sed -i -e 's/en_US.UTF-8/ja_JP.UTF-8/g' /etc/sysconfig/i18n

03-2-3. create_user.sh

・ユーザ作成

#!/bin/sh

sudo groupadd -g 1000 guest
sudo useradd -u 1000 -g 1000 guest
sudo echo password1234 | sudo passwd --stdin guest
sudo gpasswd -a guest wheel
sudo sed -i -e "s/^#(.*NOPASSWD.*)/1/" /etc/sudoers
sudo mkdir /home/guest/.ssh
sudo chmod 700 /home/guest/.ssh

秘密鍵をイメージ内に残したくないため、ここでは用意していません。

03-2-4. create_swap.sh

・Swap領域定義

#!/bin/sh

sudo dd if=/dev/zero of=/swap bs=1M count=1024
sudo mkswap /swap
sudo chmod 600 /swap
sudo swapon /swap
sudo sed -i -e '$ a /swap       swap        swap    defaults        0   0' /etc/fstab

必要があれば

03-3. テンプレート

basic-setup.json
{
    "builders":
    [
        {
            "type": "amazon-ebs",
            "ami_name": "mitzi_base_{{isotime "20060102-0304"}}",
            "region": "ap-northeast-1",
            "source_ami": "ami-2803ac4e",
            "instance_type": "t2.micro",
            "ssh_username": "ec2-user",

            "security_group_ids": ["sg-962132f1", "sg-d02033b7"],
            "vpc_id": "vpc-505ac034",
            "subnet_id": "subnet-75f7da03",
            "associate_public_ip_address": true,
            "ssh_timeout": "5m",
            "tags": {
              "OS_Version": "Amazon Linux 2017.09.01",
              "BillingType": "RESEARCH"
            }
        }
    ],
    "provisioners": [
      {
        "type": "shell",
        "scripts": [
            "./scripts/general.sh",
            "./scripts/system_setup.sh",
            "./scripts/create_user.sh",
            "./scripts/create_swap.sh"
        ]
      },
      {
         "type": "file",
         "source": "/home/guest/.ssh/authorized_keys",
         "destination": "/tmp/authorized_keys"
      },
      {
        "type": "shell",
        "inline": [
        "sudo mv /tmp/authorized_keys /home/guest/.ssh/authorized_keys",
        "sudo chmod 600 /home/guest/.ssh/authorized_keys",
        "sudo chown -R guest:guest /home/guest"
        ]
      }
    ]
}

03-3-1. ポイント

source_amiから起動したインスタンスへのSSH接続には、Packerが用意する一時的なキーペアが利用されます。

Credential keyをコード上にもたせたくなかったため、packer実行インスタンスにIAM Roleを付与しています。

・事前に用意していた公開鍵を起動したインスタンスに配置しています。
→ 「/home/guest/.ssh/」配下に直接配置しようとしましたが権限無しとエラーになるため、一旦 /tmp 配下に配置しています。 (配置先のpermissionを777にしても駄目でした)

03-4. 実行結果

・実行すると下記のようなログが標準出力されます。
(日本語設定を盛り込んでから一部文字化けするようになりました。)

・構文チェック

[root@ip-10-100-5-195 ~]# packer validate basic-setup.json 
Template validated successfully.

・build実行

[root@ip-10-100-5-195 ~]# packer build basic-setup.json 
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name: mitzi_base_20171120-0731
    amazon-ebs: Found Image ID: ami-2803ac4e
==> amazon-ebs: Creating temporary keypair: packer_5a1284ed-27b6-4c78-e7aa-9b86380a5e56
==> amazon-ebs: Launching a source AWS instance...
==> amazon-ebs: Adding tags to source instance
    amazon-ebs: Adding tag: "Name": "Packer Builder"
    amazon-ebs: Instance ID: i-01a24d283513a107a
==> amazon-ebs: Waiting for instance (i-01a24d283513a107a) to become ready...
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Provisioning with shell script: ./scripts/general.sh
    amazon-ebs: Loaded plugins: priorities, update-motd, upgrade-helper
    amazon-ebs: No packages marked for update
==> amazon-ebs: Provisioning with shell script: ./scripts/system_setup.sh
==> amazon-ebs: Provisioning with shell script: ./scripts/create_user.sh
    amazon-ebs: 繝ヲ繝シ繧カ繝シ guest 縺ョ繝代せ繝ッ繝シ繝峨r螟画峩縲
    amazon-ebs: passwd: 縺吶∋縺ヲ縺ョ隱崎ィシ繝医□繧ッ繝ウ縺梧ュ」縺励¥譖エ譁ー縺ァ縺阪∪縺励◆縲
    amazon-ebs: Adding user guest to group wheel
==> amazon-ebs: Provisioning with shell script: ./scripts/create_swap.sh
    amazon-ebs: 1024+0 繝ャ繧ウ繝シ繝牙□蜉
    amazon-ebs: 1024+0 繝ャ繧ウ繝シ繝牙□蜉
    amazon-ebs: 1073741824 繝舌う繝 (1.1 GB) 繧ウ繝斐□縺輔l縺セ縺励◆縲 14.094 遘偵 76.2 MB/遘
    amazon-ebs: 繧ケ繝ッ繝□□遨コ髢薙ヰ繝シ繧ク繝ァ繝ウ1繧定ィュ螳壹@縺セ縺吶√し繧、繧コ = 1048572 KiB
    amazon-ebs: 繝ゥ繝吶Ν縺ッ縺ゅj縺セ縺帙s, UUID=7ee19a25-bb14-40da-8115-74eaf0d0dbe5
==> amazon-ebs: Uploading /home/guest/.ssh/authorized_keys => /tmp/authorized_keys
==> amazon-ebs: Provisioning with shell script: /tmp/packer-shell459700274
==> amazon-ebs: Stopping the source instance...
    amazon-ebs: Stopping instance, attempt 1
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: mitzi_base_20171120-0731
    amazon-ebs: AMI: ami-ba2291dc
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Adding tags to AMI (ami-ba2291dc)...
==> amazon-ebs: Tagging snapshot: snap-03cf7a7d44094beb7
==> amazon-ebs: Creating AMI tags
    amazon-ebs: Adding tag: "BillingType": "RESEARCH"
    amazon-ebs: Adding tag: "OS_Version": "Amazon Linux 2017.09.01"
==> amazon-ebs: Creating snapshot tags
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-ba2291dc

・AMI作成が完了すると、一時的に起動したインスタンス(名称は「Packer Builder」)は自動で削除されます。

03-5. 確認

・意図したとおりのAMIが作成されたか、作成したAMIからインスタンスを起動して確認。

1) guestユーザの秘密鍵でSSH接続出来ることを確認
2) ユーザがsudo権限を持っていることを確認
3) TimeZoneや文字設定が意図したとおりに設定されていることを確認
4) Swapが有効になっていることを確認
5) 作成されたAMIに意図したTagが付与されていることを確認

04. 備考

04-1. 必要なPolicy

・今回はIAM Roleを利用しましたが、Packer実行に最低限必要なAWS権限は下記公式のとおりになります。
https://www.packer.io/docs/builders/amazon.html#using-an-iam-task-or-instance-role

04-2. 独自key pairをアサインしているAMIを利用した場合

・build時にssh接続出来ない問題が発生しました。
同問題は他の方も経験しているようで下記の方が仰るように公開鍵を削除する処理を盛り込みましたが、私の場合はそれでも起動しませんでした。
https://qiita.com/ikuyamada/items/97c286990adfe1770acf

そこで、下記の方のコメント欄のやりとりにあるようにPackerの独自keyを使用せず、
「key pair」及び接続時の「秘密鍵」を指定する方法にて問題解決しています。
https://qiita.com/soeda_jp/items/e9f759e2db63637e8549

おわり

今回はここまでになります。

続きを読む

AWSでterraformによる環境構築を自動化(docker)

やりたいこと

  • AWSに立てたCIサーバ(EC2・jenkins)でterraformを叩いてインフラのコード化
  • terraformはVMの環境に左右されないようコンテナ上で実行
  • imageはECRに保存しておく

設計

以下二つのジョブをjenkins上で作成
1. ECRへのイメージ自動pushのジョブ
2. イメージからコンテナを立ててterraformを実行するジョブ
スクリーンショット 2017-11-09 17.54.09.png

1に関して

ひとまずaws loginしてからpushする

Jenkinsfile
#!groovy
pipeline {
    agent any
    triggers {
        pollSCM('H/3 * * * 1-5')
    }
    //environment {}
    stages {
        stage('Master Branch <pushing>') {
            when {
                branch 'master'
            }
            steps {
                ansiColor('xterm') {
                    echo '<<< start pushing >>>'
                    sh 'aws ecr get-login --no-include-email --region ap-northeast-1 > temp.sh'
                    sh '''
                        echo "***@***.co.jp" | sudo sh temp.sh
                        sudo docker build -t ***** .
                        sudo docker tag *****:latest *****.ap-northeast-1.amazonaws.com/*****:latest
                        sudo docker push *****.ap-northeast-1.amazonaws.com/*****:latest
                    '''
                }
            }
        }
    }
}
Dockerfile(コピペなのでいらないもの入っているかもしれません…)
FROM python:3.6

ARG TERRAFORM_VERSION=0.10.0

RUN apt-get clean && \
    rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* && \
    apt-get update && \
    apt-get -y upgrade && \
    apt-get install -y --no-install-recommends unzip zip jq ca-certificates curl lsb-release gawk

# install python modules
COPY requirements.txt .
RUN pip install -r requirements.txt

# install Terraform
RUN mkdir /tmp/terraform && \
    cd /tmp/terraform && \
    curl -O -sS -L https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
    unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
    rm terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
    mv terraform* /usr/local/bin && \
    rm -rf /tmp/terraform

RUN apt-get clean && \
    rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*

2に関して

jenkinsfileのagentでコンテナを指定しようとするも失敗
– ECRからpullするには先にloginしておく必要がある
– localのイメージを使おうとしてもdocker pullをしてしまう(alwaysPull falseも効果なし)

愚直にコンテナを起動して内部でterraformを叩く…

Jenkinsfile
#!groovy
pipeline {
    agent any
    triggers {
        pollSCM('H/3 * * * 1-5')
    }
    stages {
        stage('Pull Request <Plan>') {
            when {
                branch 'PR-*'
            }
            steps {
                ansiColor('xterm') {
                    echo '<<< start planning >>>'
                    sh "docker run --name tmp-jenkins -id ***** | echo 'ignore failure'"
                    sh "docker start tmp-jenkins | echo 'ignore failure'"
                    sh "docker exec tmp-jenkins mkdir /tmp/terraform | echo 'ignore failure'"
                    sh "docker exec tmp-jenkins rm -rf /tmp/terraform/*"
                    sh "docker cp ./ tmp-jenkins:/tmp/terraform/"
                    sh "docker exec tmp-jenkins ls /tmp/terraform/"
                    sh "docker exec tmp-jenkins sh -c 'cd /tmp/terraform/ && terraform init -backend-config \"bucket=*****\"'"
                    sh "docker exec tmp-jenkins sh -c 'cd /tmp/terraform/ && terraform env select dev'"
                    sh "docker exec tmp-jenkins sh -c 'cd /tmp/terraform/ && terraform plan'"
                    sh "docker exec tmp-jenkins sh -c 'cd /tmp/terraform/ && terraform apply'"
                    sh "docker stop tmp-jenkins"
                }
            }
        }
    }
}

結果

  • ひとまずterraformを動かすことができた
  • ただ何かを作ろうとするとエラーが出る…
     * provider.aws: dial unix /tmp/plugin392670482|netrpc: connect: no such file or directory
  • あとこれを解決すれば完成… =>terraformを 0.10.0 -> 0.10.8にすることで解決

続きを読む

マストドンを2.0.0へバージョンアップを行う

概要

既に動いているインスタンスを2.0.0へ上げる
動かし方としてはEC2上から docker-compose up -d でそのまま使っている。
バージョンを上げるついでに画像データはS3へ、DBはRDSへ同時に移行させる。

環境

$ cat /proc/version
Linux version 4.4.0-1039-aws (buildd@lcy01-02) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4) ) #48-Ubuntu SMP Wed Oct 11 15:15:01 UTC 2017
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
$ docker -v
Docker version 17.05.0-ce-rc3, build 90d35ab
$ docker-compose -v
docker-compose version 1.12.0, build b31ff33
$ git describe --tags #マストドン
v1.4rc3-24-gbbc3db8

やったこと

RDSの作成

  • 時間がかかるので一番最初に作成してしまう。
  • 何らかの方法で適宜RDS(PostgreSQL)を作成。
    • 特に設定はしていない。文字コードぐらい?

S3の作成

  • コンソールからS3を作成
  • バケットポリシーを変更
バケットポリシー
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Sid":"PublicS3Objects",
      "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::<YOUR BUCKET NAME>/*"]
    }
  ]
}
  • S3へ既存の画像データをアップロード

    • $ aws s3 sync ./public/system/ s3://<YOUR BUCKET NAME>/

既存DBのダンプを取得

  • docker内からダンプを取得するため、 docker-compose.yml を書き換える

    • dockerとバージョンの合ったpsqlコマンドを取得するのが面倒なため中から実行する。
  db:
    restart: always
    image: postgres:9.6-alpine
### Uncomment to enable DB persistance
    volumes:
      - ./postgres:/var/lib/postgresql/data
      - ./dump:/dump
  • ダンプを取得する

    • $ docker-compose run --rm db pg_dump -h localhost -p 5432 -U <USER NAME> <DB NAME> > /dump/dump.sql
  • 取得したダンプをRDSへ流し込む
    • $ psql -h <RDS ENDPOINT> -U <USER NAME> <DB NAME> < ./dump/dump.sql

リポジトリをクローンし直す

  • 現在動いているマストドンを停止させる

    • $ docker-compose stop
  • 適当なディレクトリに移動し、マストドンをcloneする

    $ cd /opt
    $ git clone https://github.com/tootsuite/mastodon
    $ cd mastodon
    

.env.productionの編集

  • 運用していたマストドンディレクトリから .env.production を取得

    • $ cp /path/to/mastodon/.env.production ./
  • .env.production を編集

    • 編集するのは DB パラメータと、 S3 パラメータ。
    • S3はENDPOINTは以下の通り編集する必要あり
      • ex) S3_ENDPOINT=https://s3-ap-northeast-1.amazonaws.com
  • VAPIDを埋める
    • $ docker-compose run --rm web rake mastodon:webpush:generate_vapid_key を実行して出力されたパラメータをコピペ

DBのマイグレーションとアセットのコンパイル

  • DBのマイグレーションの実行とアセットのコンパイル

    • $ docker-compose run --rm web rake db:migrate; docker-compose run --rm web rake assets:precompile

マストドンの起動

  • $ docker-compose up -d

参考

続きを読む