AWS Lambda(Python)によるサーバー簡易監視と、Slackでの結果通知

実現したかったこと

  • 運用中サーバーの簡易監視として、定期的にHTTPアクセスを行い、結果を確認したい。
  • サーバーレスで実行したい。
    • 対象サーバーにアクセスするIPを制限しているため、監視元は固定IPの必要がある。
  • 完全自動化し、監視結果だけが通知されて欲しい。

最終的に実現したこと

  • 全体イメージ
    全体イメージ

  • S3に監視先サーバーデータを配置する。

    • データの形式は以下の通り。

      servers.json
      {
          "servers": [
              {"name": "Google", "url": "http://www.google.co.jp"},
              {"name": "Yahoo", "url": "http://www.yahoo.co.jp"}
          ]
      }
      
  • AWS Lambda(Python)から上記のデータを読み込み、各サーバーにHTTPアクセスを行う。

  • 監視結果をSlackに通知する。

  • 上記を、CloudWatch Events - Scheduleで定期実行する。

設定詳細

VPC設定

IAM設定

  • Lambda作成時に、roleのtemplateとして、Simple Microservice permissionsを選択。
  • S3にアクセスするため、AmazonS3ReadOnlyAccessのポリシーをアタッチ。
  • LambdaVPC上で実行するため、AWSLambdaVPCAccessExecutionRoleのポリシーをアタッチ。

CloudWatch Events – Schedule設定

Slack

AWS Lambda(Python)

  • エラーハンドリング周りは改善の余地あり。
lambda_function.py
import json
import requests
import boto3

BUCKET_NAME = 'xxxxxxxxxx'
OBJECT_NAME = 'xxxxxxxxxx/servers.json'
SLACK_POST_URL = 'https://hooks.slack.com/services/xxxxxxxxxx/xxxxxxxxxx/xxxxxxxxxxxxxxxxxxxx'

def lambda_handler(event, context):
    json_data = __getServers()
    __check_server(json_data)

def __getServers():
   s3 = boto3.resource('s3')
   obj = s3.Object(BUCKET_NAME, OBJECT_NAME)
   response = obj.get()
   body = response['Body'].read()
   return body.decode('utf-8')

def __check_server(json_data):
    data = json.loads(json_data)
    servers = data['servers']

    has_error = False

    for server in servers:
        name = server['name']
        url = server['url']
        print("Check: " + name)

        try:
            r = requests.get(url)
            if r.status_code != 200:
                __send_error_message(name, url)
                has_error = True
        except requests.exceptions.RequestException as e:
            __send_request_error_message(name, url)
            has_error = True

    if has_error == False:
        __send_success_message()

def __send_error_message(name, url):
    payload = {
        "text": name + 'n' + url + 'n' + '*ERROR!*',
        "icon_emoji": ":x:"
    }
    __send_message(payload)

def __send_request_error_message(name, url):
    payload = {
        "text": name + 'n' + url + 'n' + '*Request Error!*',
        "icon_emoji": ":warning:"
    }
    __send_message(payload)

def __send_success_message():
    payload = {
        "text": "All Servers OK!",
        "icon_emoji": ":o:"
    }
    __send_message(payload)

def __send_message(payload):
    try:
        return requests.post(SLACK_POST_URL, json=payload)
    except requests.exceptions.RequestException as e:
        return None


AWS Lambda(Python)設定時の注意点

  • ライブラリはソースコードと同じディレクトリに配置する。
pip install requests -t .
  • ソースコードとライブラリをまとめて、zipにして配置する。
zip -r lambda_function.zip *

雑感

  • AWS LambdaにVPCを設定できるようになったことで、データ送信元を固定IPアドレスにしてLambdaを実行できるようになったため、使いどころが広がった。
  • 手軽な通知先として、Slackとの連携が簡単で便利すぎ。
  • servers.jsonの中身をbodyに詰めた、HTTP POST通信をトリガーに、API Gateway経由で実行させる方法も試したが、最終的に定期実行がラクな現在の形とした。

続きを読む

htaccessでHTTPSにリダイレクトする

エンジニアたるものhttpは使わないようにしましょう。使う意味がありません。
しかし、世の中にはすでにhttpで運営されてるサイトやAPIがたくさんあります。
そこで今回はすでにhttpだけど、httpsでの運用に切り替えるときに使うhtaccessを書こうと思います。
(検索しても意外と決定版みたいなのないので)

単純な場合

これはapacheが1台の構成の場合のものです。特に上流にバランサ(ELBなど)がない場合を想定しています。
下記のような内容で.htaccessを書きましょう。

/var/www/html/.htaccess
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

このなかでRewriteRuleの最後に書いてある[R=301,L]のとこなのですが、Rがリダイレクトを指しており、=301を省略すると302がデフォルトになります。
用途に応じて使い分けると良いでしょう。

  • [R=301,L] 永久的なHTTP→HTTPS
  • [R,L] 一時的なリダイレクト

ロードバランサー(80,443対応)の配下にある場合

これはAWS環境でELBがある場合や、その他のプロプライエタリなロードバランサ(LoadMaster、BigIP、Coyote、A10など)の場合に適用できます。もちろんNginxなどのリバースプロキシを使う場合も有効です。
基本的には上流のバランサーにHTTP/HTTPSに応じてリクエストヘッダーを付与してもらいます。
apacheではその内容に応じたRewriteCondを記述します。

例えばAWS環境のELBの場合は下記のようになります。

/var/www/html/.htaccess
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

2行目のRewriteCondはHTTPだった場合に付加されるリクエストヘッダーを想定しています。これは製品によって異なりますが、%{HTTP:X-Forwarded-Proto}はAWSのELBの標準的なリクエストヘッダーです。

まとめ

apacheでやる限りだいたい上記の2パターンで事が足りるでしょう。もし細かく制御したいケースやご要望などあれば追記しますので、コメントなどでどうぞ。

続きを読む

AWSPowershell.NetCore on PowerShell on macOS 環境構築

普段、職場やプライベートでAWSを利用する場合、Windowsマシンでaws-cliではなくAWSPowerShell(aws-cliのPowerShell版)を利用しています。 自宅のブレーカーが落ちてWindowsマシンが逝ってしまったため、やむを得ず サブ機のMacBookでも同じ環境でAWSを使えたらいいな、と思い、OSS/クロスプラットフォーム化したPowerShellおよびその上で稼働するAWSPowerShell.NetCoreをmacOSに入れてみました。以下、環境構築時の作業メモです。

前提環境

  • macOS Sierra
  • homebrewインストール済
  • homebrew-caskインストール済

環境構築手順

openssl & curl (by homebrew) インストール

macOSに同梱されているopensslはApple社独自開発のもので、一般的なLinuxディストリビューション等で利用されているopensslとは別物のようです。PowerShellは後者の方が相性がいいようなので、homebrewで後者に相当するものをインストールします。

wukann@mac ~$ brew install openssl
wukann@mac ~$ brew install curl --with-openssl

.NET Core requires Homebrew’s OpenSSL because the “OpenSSL” system libraries on macOS are not OpenSSL, as Apple deprecated OpenSSL in favor of their own libraries. This requirement is not a hard requirement for all of PowerShell; however, most networking functions (such as Invoke-WebRequest) do require OpenSSL to work properly.

蛇足:AppleとOpenSSL

AppleがOpenSSLを利用しなくなった経緯についてはこちら↓を参照。

PowerShellインストール

macOS向けのインストーラもあるようですが、今回はhomebrew-caskを利用します。

PowerShellインストール
wukann@mac ~$ brew cask install powershell
==> Caveats
A OpenSSL-backed libcurl is required for custom handling of certificates.
This is rarely needed, but you can install it with
  brew install curl --with-openssl
See https://github.com/PowerShell/PowerShell/issues/2211

==> Satisfying dependencies
==> Installing Formula dependencies from Homebrew
openssl ... already installed
complete
==> Downloading https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.14/powershell-6.0.0-alpha.14.pkg
######################################################################## 100.0%
==> Verifying checksum for Cask powershell
==> Running installer for powershell; your password may be necessary.
==> Package installers may write to any location; options such as --appdir are ignored.
Password:
==> installer: Package name is powershell-6.0.0-alpha.14
==> installer: Installing at base path /
==> installer: The install was successful.
🍺  powershell was successfully installed!

一応、インストールされていることを確認。

PowerShellインストール確認
wukann@mac ~$ which powershell
/usr/local/bin/powershell

AWSPowershell.NetCoreインストール

PowerShellにもパッケージ(モジュール)管理機能「PowerShellGet」があります。今回インストールしたPowerShell v6には最初から入っているようです。これを利用してAWSPowerShell.NetCoreをインストールします。

PowerShellセッション起動

ここからはmacOSのshellセッションからPowerShellセッションを起動し、その上でPowerShellコマンド操作をします。

PowerShellセッション起動
wukann@mac ~$ powershell
PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.

PS /Users/wukann>

PowerShellリポジトリ設定(PSGallery)

PowerShellGetでモジュールをインストールする場合、事前にモジュールを公開しているリポジトリを登録する必要があります。今回インストールするAWSPowerShell.NetCoreは、Microsoft公式のPowerShell Gallery(PSGallery)で公開されています。
登録済みリポジトリの情報はGet-PSRepositoryコマンドで取得できます。

登録済みPowerShellリポジトリ確認
PS /Users/wukann> Get-PSRepository

Name                      InstallationPolicy   SourceLocation
----                      ------------------   --------------
PSGallery                 Untrusted            https://www.powershellgallery.com/api/v2/

PowerShell Gallery(PSGallery)は最初から登録されていますが、InstallationPolicyUntrustedに設定されています。このままでもPSGalleryからモジュールをインストールできますが、インストールの度に「信頼できなリポジトリからモジュールをインストールしますか?」という確認メッセージにY/N対応する必要があります。
今回はPSGalleryを「信頼済みのリポジトリ」として設定します。

PSGalleryを信頼済みリポジトリに設定
PS /Users/wukann> Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

設定が反映されていることを確認。

PSGallery設定確認
PS /Users/wukann> Get-PSRepository

Name                      InstallationPolicy   SourceLocation
----                      ------------------   --------------
PSGallery                 Trusted              https://www.powershellgallery.com/api/v2/

AWSPowershell.NetCoreインストール

リポジトリの設定が済んだところで、Install-ModuleコマンドでAWSPowerShell.NetCoreをインストールします。

AWSPowerShell.NetCoreインストール
PS /Users/wukann> Install-Module -Name AWSPowerShell.NetCore -Scope CurrentUser

念のため、Get-InstalledModuleコマンドでインストール済みモジュールを確認。

インストール済みモジュールの確認
PS /Users/wukann> Get-InstalledModule

Version    Name                                Repository           Description
-------    ----                                ----------           -----------
1.1.2.0    PackageManagement                   https://powershel... PackageManagement (a.k.a. OneGet) is a new way to discover and in...
1.1.2.0    PowerShellGet                       https://powershel... PowerShell module with commands for discovering, installing, upda...
3.3.38.0   AWSPowerShell.NetCore               PSGallery            The AWS Tools for PowerShell Core lets developers and administrat...


補足・-Scopeオプションなし or -Scope AllUsersを指定した場合

今回、Install-Moduleコマンドに-Scope CurrentUserオプションを付けてモジュールをインストールしました。このオプションがない場合、-Scope AllUsersがデフォルトで指定されます。んで、そうするとこの↓ようにエラーとなります。

-ScopeをCurrentUserにしないで実行した場合
PS /Users/wukann> Install-Module -Name AWSPowerShell.NetCore -Scope AllUsers
PackageManagementInstall-Package : Administrator rights are required to install modules in '/usr/local/microsoft/powershell/6.0.0-alpha
.14/Modules'. Log on to the computer with an account that has Administrator rights, and then try again, or install '/Users/wukann/.local
/share/powershell/Modules' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with
elevated rights (Run as Administrator).
At /usr/local/microsoft/powershell/6.0.0-alpha.14/Modules/PowerShellGet/1.1.2.0/PSModule.psm1:1809 char:21
+ ...          $null = PackageManagementInstall-Package @PSBoundParameters
+                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (Microsoft.Power....InstallPackage:InstallPackage) [Install-Package], Exception

エラーメッセージを見る限りではPowerShellをsudoで起動すれば解決する気がしますが、「ここまでsudo無しで進めてこられたのにここだけsudoするのもなぁ…」ということで試していません。

AWSPowershell.NetCoreインポート

Notice:ここで取り上げているモジュールのインポート(Import-Module)は、そのセッション内でのみ有効です。powershellセッションを立ち上げ直した場合、再度インポートし直す必要があります。
(この件に関する対処方法もいつか書ければ…)

ここまでの手順でAWSPowershell.NetCoreをインストールできましたが、この状態ではコマンドを利用できません。PowerShellセッションにモジュールが読み込まれていないためです。セッションに読み込まれ、利用可能なモジュールを確認するには、Get-Moduleコマンドを実行します。

利用可能モジュール確認
PS /Users/wukann> Get-Module

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Manifest   3.1.0.0    Microsoft.PowerShell.Management     {Add-Content, Clear-Content, Clear-Item, Clear-ItemProperty...}
Manifest   3.1.0.0    Microsoft.PowerShell.Utility        {Add-Member, Add-Type, Clear-Variable, Compare-Object...}
Script     1.1.2.0    PackageManagement                   {Find-Package, Find-PackageProvider, Get-Package, Get-PackageProvider...}
Script     1.1.2.0    PowerShellGet                       {Find-Command, Find-DscResource, Find-Module, Find-RoleCapability...}
Script     1.2        PSReadLine                          {Get-PSReadlineKeyHandler, Get-PSReadlineOption, Remove-PSReadlineKeyHandle...

AWSPowershell.NetCoreが一覧になく、セッションに読み込まれていないことが確認できます。
モジュールをセッションに読み込むには、Import-Moduleを実行します。

AWSPowerShell.NetCoreをPowerShellセッションに読み込む
PS /Users/wukann> Import-Module AWSPowerShell.NetCore

再度Get-Moduleコマンドを実行すると、AWSPowerShell.NetCoreがセッションに読み込まれたことが確認できます。

Get-Module
PS /Users/wukann> Get-Module

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Binary     3.3.38.0   AWSPowerShell.NetCore               {Add-AASScalableTarget, Add-ACMCertificateTag, Add-ADSConfigurationItemsToA...
Manifest   3.1.0.0    Microsoft.PowerShell.Management     {Add-Content, Clear-Content, Clear-Item, Clear-ItemProperty...}
Manifest   3.1.0.0    Microsoft.PowerShell.Utility        {Add-Member, Add-Type, Clear-Variable, Compare-Object...}
Script     1.1.2.0    PackageManagement                   {Find-Package, Find-PackageProvider, Get-Package, Get-PackageProvider...}
Script     1.1.2.0    PowerShellGet                       {Find-Command, Find-DscResource, Find-Module, Find-RoleCapability...}
Script     1.2        PSReadLine                          {Get-PSReadlineKeyHandler, Get-PSReadlineOption, Remove-PSReadlineKeyHandle...

実際にAWSPowerShell.NetCoreのコマンド(Get-AWSPowerShellVersion)を実行してみます。

AWSPowerShell.NetCoreのコマンド実行
PS /Users/wukann> Get-AWSPowerShellVersion

AWS Tools for PowerShell Core
Version 3.3.38.0
Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Amazon Web Services SDK for .NET
Core Runtime Version 3.3.7.1
Copyright 2009-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Release notes: https://aws.amazon.com/releasenotes/PowerShell

This software includes third party software subject to the following copyrights:
- Logging from log4net, Apache License
[http://logging.apache.org/log4net/license.html]

ヨッシャ(*´ω`*)

続きを読む

EC2(Python3)->BigQuery

前提

・GCPのアカウントが準備できていること
・BigQueryに接続可能なtableが存在すること

実行環境の準備

ec2
-pyenv
-naconda3-4.0.0
-pandas
-httplib2
-google-api-python-client

モジュール追加

sudo su -
pyenv global naconda3-4.0.0
pip install httplib2
pip install google-api-python-client

GoogleCloudSDKの導入(python2.x環境で実行)と認証

pyenv global system
curl https://sdk.cloud.google.com | bash
~/google-cloud-sdk/bin/gcloud auth login

※認証情報は~/.config配下に出力される

ごく少ないレコードのテーブルで実行

import pandas as pd
query = 'SELECT * FROM dataset_name.table_name'
pd.read_gbq(query, 'your_project_ID')

→pd.read_gbqで認証が走るが、EC2が実行元となるため、
 localhostでリスンする認証用URLがうまく受け取れない

Oauth2での認証情報生成

from oauth2client.client import OAuth2WebServerFlow
from oauth2client.file import Storage
flow = OAuth2WebServerFlow(client_id='your_client_id',
                           client_secret='your_client_secret',
                           scope='https://www.googleapis.com/auth/bigquery',
                           redirect_uri='urn:ietf:wg:oauth:2.0:oob')
storage = Storage('bigquery_credentials.dat')
authorize_url = flow.step1_get_authorize_url()
print 'Go to the following link in your browser: ' + authorize_url
code = raw_input('Enter verification code: ')
credentials = flow.step2_exchange(code)
storage.put(credentials)

※your_client_id、your_client_sercretは~/.config配下から

上記処理実行後、カレントディレクトリに「bigquery_credentials.dat」が作成される。
→pandas.read_gbqは上記を認証情報として利用

ごく少ないレコードのテーブルで実行

import pandas as pd
query = 'SELECT * FROM dataset_name.table_name'
pd.read_gbq(query, 'your_project_ID')

参考

https://developers.google.com/api-client-library/python/guide/aaa_oauth
http://stackoverflow.com/questions/37792709/accessing-big-query-from-cloud-datalab-using-pandas

続きを読む

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

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

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

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

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

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

Chef [Berkshelf]

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

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

の3つをやれば良い。

ディレクトリ構成

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

VagrantでCentOS 6.7の環境設定

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

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

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

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

default: Warning: Authentication failure. Retrying...

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

vagrant ssh

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

cd /home
chmod 755 -R ./vagrant
exit

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

Vagrantを立ち上げる。

vagrant up

Vagrantfileの設定

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

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

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

Gemfileの作成(※)

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

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

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

Berksfileの作成(※)

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

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

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

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

Recipeの作成(※)

nginxのレシピ

1.Cookbookの作成。

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

2.Recipeファイルの作成。

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

package "nginx" do
  action :install
end

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

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

3.attributeファイルの作成。

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

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

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

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


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;

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

    server {
        listen       80 default_server;
        server_name  _;

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

Rubyのレシピ

1.Cookbookの作成。

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

2.Recipeファイルの作成。

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

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

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

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

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

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


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


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

3.attributeファイルの作成。

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

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

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

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

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

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

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

Node.jsのレシピ

1.Cookbookの作成。

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

2.Recipeファイルの作成。

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

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

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

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

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

Railsの動作確認

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

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

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

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


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

gem 'unicorn'

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

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

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

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

bundle install

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

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

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

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

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

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

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

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

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

OpsWorksでデプロイ

Berkshelfによるパッケージ化

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

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

OpsWorks(OpsWorks Stacks)の操作

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

Stackの設定

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

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

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

Layerの設定

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

[Add layer]を選択。

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

[Save]を選択。

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

Instanceの作成

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

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

[Add Instance]を選択。

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

補足

Berkshelfの実行

bundle exec berks install --path ./cookbooks

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

bundle exec berks vendor --path ./cookbooks

注意点

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

rbenv shell 2.3.1

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

Ansible

導入

インストール

brew install ansible

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

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

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

ディレクトリ構成

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

ファイル内容

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


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

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

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

    server_tokens {{ nginx_server_tokens }};

    sendfile        on;

    keepalive_timeout  65;

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

    index   index.html index.htm;

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

   server {
        listen 80;
        server_name <IP address>;

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

実行

ansible-playbook sample.yml

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

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

注意点

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

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

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

Docker [Docker Compose]

FROM ruby:2.3.1

ENV APP_ROOT /app

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

RUN mkdir $APP_ROOT
WORKDIR $APP_ROOT

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

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

ビルド & 実行

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

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

ビルド

docker-compose build

実行

docker-compose up

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

docker-machine ip

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

ECSでデプロイ

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

マニュアルで環境構築

Railsの環境準備

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

Webサーバのインストール

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

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

gitの準備

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

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

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

Webサーバの環境・設定

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

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

<%= ENV[‘DATABASE_PASSWORD_PRODUCT’] %>

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

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

参考

Chef

Ansible

Docker

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

続きを読む

AWS IoT デバイス死活監視

はじめに

前回の記事で簡単にPythonでIoTデバイスのエミュレーションが可能になったことを説明させていただきました。

今回はAWS IoTのPub/Subの仕組みを利用してデバイスの死活監視を簡単に行ってみたいと思います。

前準備

前回の記事で作成したthingを利用しますので、内容を実施しておいてください。

デバイス側(Python)の実装

デバイス側は、クライアント証明書や、デバイスのUUIDを指定して実行することを想定しています。死活監視用のtopicをデバイス側で登録して、そこにメッセージが入れば、返信するという単純な実装になっています。

以下、Pythonのサンプルコードです

class AWSIoTClient:
    def __init__(self, endpoint, rootCA, key, cert, uuid):
        self.myMQTTClient = AWSIoTMQTTClient("python-thing")
        self.myMQTTClient.configureEndpoint(endpoint, 8883)
        self.myMQTTClient.configureCredentials(rootCA, key, cert)
        self.myMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
        self.myMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
        self.myMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
        self.myMQTTClient.configureMQTTOperationTimeout(5)

        self.notificationTopic = uuid + "/notification"
        self.eventTopic = uuid + "/event"

    def start(self):
        self.myMQTTClient.connect()
        print "Wating for : " + self.notificationTopic
        self.myMQTTClient.subscribe(self.notificationTopic, 1, self.customCallback)

    def customCallback(self, client, userdata, message):
        print message.payload
        callbackMessage = "{n    I'm healthyn}"
        client.publish(self.eventTopic, callbackMessage, 1)

def main():
    args = Args()
    client = AWSIoTClient(args.getEndpoint(), args.getRootCA(), args.getKey(), args.getCert(),
                          args.getUUID())
    client.start()
    while True:
        time.sleep(1)

MQTTClientのsubscribeメソッドで”UUID/notification”トピックを監視しています。トピックにメッセージが書き込まれると、customCallbackメソッドが呼び出され、”UUID/event”トピックにメッセージを記述します。これにより死活監視が単純に実現されています。

subscribeメソッドの第二引数の1という値は、MQTTのQOSを表しています。

  • 0: At most once 最高1回。到達を保証しない
  • 1: At least once 少なくとも1回の到達を保証。重複の可能性あり
  • 2: Exactly once 確実に一度の転送

MQTClientのAPIリファレンス

pythonアプリの実装は、以下のような形で動作させる

python python-thing.py -e XXXX.iot.ap-northeast-1.amazonaws.com -r root-CA.crt -c test_thing.cert.pem -k test_thing.private.key -u 1234

AWS IoT コンソールを使った動作確認

デバイス側の準備が整ったので、後はAWS IoTコンソールからメッセージをトピックに送信すれば良い。AWS IoTコンソールでは”Test”を選択すればトピックにメッセージを送信”Publish”もしくはメッセージの読み取り”Subscribe”が可能。

まずは、1234/eventトピックにデバイスからのヘルスチェックで返信されるので、以下のようにSubscribeする。

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

そして以下のようにデバイスが待ち受けている1234/notificationトピックにメッセージを送信。

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

デバイス側がトピックのメッセージを受信して、1234/eventに以下のように返信する。

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

おわりに

特定のトピックにメッセージが来た場合に別のトピックにメッセージを返すことで簡単に死活監視が実現できる。これが一人で数時間で実現できるAWS IoT素晴らしいです。

続きを読む