AWS CloudWatch で、Amazon Linux のパフォーマンスとログの監視をしてみる

0.はじめに

Amazon Linuxを利用していますが、
パフォーマンス監視は Zabbix を使って、
ログ監視は特に何も、
という感じでした。

CloudWatch のメトリクスの保存期間も長くなったみたいですし、
運用の手間やリスク、コスト削減も考慮して、
パフォーマンス監視を CloudWatch、
ログ監視を CloudWatch Logs、
にしようかと思います。

1.IAM Role へのポリシーのアタッチ

  1. 以下の IAM ポリシーを作成します。

    • ポリシー名 : GSCloudWatchWriteOnlyAccess ※ 任意
    • 説明 : ※ 任意
    • ポリシードキュメント :
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "Stmt1459146265000",
                "Effect": "Allow",
                "Action": [
                    "cloudwatch:PutMetricData"
                ],
                "Resource": [
                    "*"
                ]
            },
            {
                "Sid": "Stmt1459146665000",
                "Effect": "Allow",
                "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                ],
                "Resource": [
                    "arn:aws:logs:*:*:*"
                ]
            }
        ]
    }
    

  2. 作成したポリシーを EC2 インスタンスに割り当てられている IAM Role に付与します。

1.CloudWatch へのメトリクスデータの送信

  1. 色々調べたところ、collectd と その CloudWatch 用プラグインを利用するのが一般的みたいなので、今回はその手順で進めていきます。

  2. collectd をインストールします。

    $ sudo yum -y install collectd
    

  3. collectd の CloudWatch 用プラグインをインストールします。

    $ git clone https://github.com/awslabs/collectd-cloudwatch.git
    $ cd collectd-cloudwatch/src
    $ sudo ./setup.py
    
    Installing dependencies ... OK
    Installing python dependencies ... OK
    Downloading plugin ... OK
    Extracting plugin ... OK
    Moving to collectd plugins directory ... OK
    Copying CloudWatch plugin include file ... OK
    
    Choose AWS region for published metrics:
      1. Automatic [ap-northeast-1]
      2. Custom
    Enter choice [1]: 
    
    Choose hostname for published metrics:
      1. EC2 instance id [i-00484bb5ac67e244d]
      2. Custom
    Enter choice [1]: 
    
    Choose authentication method:
      1. IAM Role [testuekamawindowsserver]
      2. IAM User
    Enter choice [1]: 
    
    Enter proxy server name:
      1. None
      2. Custom
    Enter choice [1]: 
    
    Enter proxy server port:
      1. None
      2. Custom
    Enter choice [1]: 
    
    Include the Auto-Scaling Group name as a metric dimension:
      1. No
      2. Yes
    Enter choice [1]: 
    
    Include the FixedDimension as a metric dimension:
      1. No
      2. Yes
    Enter choice [1]: 
    
    Enable high resolution:
      1. Yes
      2. No
    Enter choice [2]: 
    
    Enter flush internal:
      1. Default 60s
      2. Custom
    Enter choice [1]: 
    
    Choose how to install CloudWatch plugin in collectd:
      1. Do not modify existing collectd configuration
      2. Add plugin to the existing configuration
      3. Use CloudWatch recommended configuration (4 metrics)
    Enter choice [3]: 
    Plugin configuration written successfully.
    Creating backup of the original configuration ... OK
    Replacing collectd configuration ... OK
    Replacing whitelist configuration ... OK
    Stopping collectd process ... NOT OK
    Starting collectd process ... NOT OK
    Installation cancelled due to an error.
    Executed command: '/usr/sbin/collectd'.
    Error output: 'Error: Reading the config file failed!
    Read the syslog for details.'.
    

  4. collectd の起動に失敗しています。collectd の python 用ライブラリが足りないみたいなので、インストールします。

    $ sudo yum -y install collectd-python
    

  5. collectd を起動します。

    $ sudo service collectd start
    

  6. collectd の自動起動の設定をします。

    $ sudo chkconfig collectd on
    $ sudo chkconfig --list | grep collectd
    
    collectd           0:off    1:off    2:on    3:on    4:on    5:on    6:off
    

  7. /etc/collectd.conf の設定を変更します。

    $ sudo cp -frp /etc/collectd.conf /etc/collectd.conf.ORG
    $ sudo vi /etc/collectd.conf
    
    • cpu :

      • LoadPlugin cpu をコメント解除し、以下の設定を行う。
      <Plugin cpu>
              ReportByCpu false
              ReportByState true
              ValuesPercentage true
      </Plugin>
      
    • df :

      • LoadPlugin df をコメント解除し、以下の設定を行う。
      <Plugin df>
      #       Device "/dev/hda1" 
      #       Device "192.168.0.2:/mnt/nfs" 
      #       MountPoint "/home" 
      #       FSType "ext3" 
      #       IgnoreSelected false
              ReportByDevice false
              ReportInodes false
              ValuesAbsolute true
              ValuesPercentage true
      </Plugin>
      
    • load :

      • LoadPlugin load をコメント解除し、以下の設定を行う。
      <Plugin load>
              ReportRelative true
      </Plugin>
      
    • memory :

      • LoadPlugin memory をコメント解除し、以下の設定を行う。
      <Plugin memory>
              ValuesAbsolute true
              ValuesPercentage true
      </Plugin>
      
    • swap :

      • LoadPlugin swap をコメント解除し、以下の設定を行う。
      <Plugin swap>
              ReportByDevice false
              ReportBytes false
              ValuesAbsolute false
              ValuesPercentage true
      </Plugin>
      

  8. /opt/collectd-plugins/cloudwatch/config/whitelist.conf の設定を変更します。以下のメトリクスの中で不要なものがあれば、適当に削除して下さい。

    $ cd /opt/collectd-plugins/cloudwatch/config/
    $ sudo cp -frp whitelist.conf whitelist.conf.ORG
    $ sudo vi whitelist.conf
    
    cpu-.*
    df-root-df_complex-free
    df-root-df_complex-reserved
    df-root-df_complex-used
    df-root-percent_bytes-free
    df-root-percent_bytes-reserved
    df-root-percent_bytes-used
    load--load-relative
    memory--percent-free
    memory--percent-used
    memory--memory-free
    memory--memory-used
    swap--percent-cached
    swap--percent-free
    swap--percent-used
    

  9. collectd を再起動します。

    $ sudo service collectd restart
    

  10. CloudWatch のマネジメントコンソールの左側ペインから、「メトリクス」を選択します。カスタム名前空間の「collectd」→「Host, PluginInstance」→ EC2 インスタンスの ID でフィルタをかけて、設定したメトリクスのデータがあるか確認します。

    • FireShot Capture 165 - CloudWatch Management Console_ - https___ap-northeast-1.console.aws.png

    • FireShot Capture 166 - CloudWatch Management Console_ - https___ap-northeast-1.console.aws.png

    • FireShot Capture 171 - CloudWatch Management Console_ - https___ap-northeast-1.console.aws.png

2.CloudWatch Logs へのログデータの送信

  1. awslogs をインストールします。

    $ sudo yum -y install awslogs
    

  2. /etc/awslogs/awscli.conf の設定を変更します。

    $ sudo cp -frp /etc/awslogs/awscli.conf /etc/awslogs/awscli.conf.ORG
    $ sudo vi /etc/awslogs/awscli.conf
    
    [plugins]
    cwlogs = cwlogs
    [default]
    region = ap-northeast-1
    

  3. /etc/awslogs/awslogs.conf の設定を変更します。

    • apache や nginx の設定もしています。不要であれば、削除して下さい。
    $ sudo cp -frp /etc/awslogs/awslogs.conf /etc/awslogs/awslogs.conf.ORG
    $ sudo vi /etc/awslogs/awslogs.conf
    
    [/var/log/messages]
    datetime_format = %b %d %H:%M:%S
    file = /var/log/messages
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/var/log/messages
    
    [/var/log/cron]
    datetime_format = %b %d %H:%M:%S
    file = /var/log/cron
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/var/log/cron
    
    [/etc/httpd/logs/access_log]
    datetime_format = [%a %b %d %H:%M:%S %Y]
    file = /etc/httpd/logs/access_log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/etc/httpd/logs/access_log
    
    [/etc/httpd/logs/error_log]
    datetime_format = [%a %b %d %H:%M:%S %Y]
    file = /etc/httpd/logs/error_log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/etc/httpd/logs/error_log
    
    [/etc/httpd/logs/ssl_access_log]
    datetime_format = [%a %b %d %H:%M:%S %Y]
    file = /etc/httpd/logs/ssl_access_log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/etc/httpd/logs/ssl_access_log
    
    [/etc/httpd/logs/ssl_error_log]
    datetime_format = [%a %b %d %H:%M:%S %Y]
    file = /etc/httpd/logs/ssl_error_log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/etc/httpd/logs/ssl_error_log
    
    [/etc/httpd/logs/ssl_request_log]
    datetime_format = [%a %b %d %H:%M:%S %Y]
    file = /etc/httpd/logs/ssl_request_log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/etc/httpd/logs/ssl_request_log
    
    [/var/log/nginx/access.log]
    datetime_format = %Y/%m/%d %H:%M:%S
    file = /var/log/nginx/access.log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/var/log/nginx/access.log
    
    [/var/log/nginx/backend.access.log]
    datetime_format = %Y/%m/%d %H:%M:%S
    file = /var/log/nginx/access.log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/var/log/nginx/backend.access.log
    
    [/var/log/nginx/badactor.log]
    datetime_format = %Y/%m/%d %H:%M:%S
    file = /var/log/nginx/badactor.log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/var/log/nginx/badactor.log
    
    [/var/log/nginx/error.log]
    datetime_format = %Y/%m/%d %H:%M:%S
    file = /var/log/nginx/error.log
    buffer_duration = 5000
    log_stream_name = {instance_id}
    initial_position = start_of_file
    log_group_name = AmazonLinux/var/log/nginx/error.log
    

  4. awslogs を起動します。

    $ sudo service awslogs start
    

  5. awslogs の自動起動の設定をします。

    $ sudo chkconfig awslogs on
    $ sudo chkconfig --list | grep awslogs
    
    awslogs            0:off    1:off    2:on    3:on    4:on    5:on    6:off
    

99.ハマりポイント

  • 今回は、凡ミスばかりで本当に自分が嫌になりました…。もう、毎回、何やってんだ…。

  • CloudWatch へのメトリクスデータの送信では、 CloudWatch のカスタム名前空間の「collectd」ではなく、AWS の EC2 のフィルタに表示されると勘違いして、全然ログが出てこないと悩んでいました…。もう、本当馬鹿…。
  • 後、/etc/collectd.conf の設定も結構悩みました。
  • CloudWatch Logs へのログデータの送信では、/etc/awslogs/awscli.conf の設定を /etc/awslogs/awslogs.conf にすると勘違いして、本当に無駄な時間を浪費しました…。

XX.まとめ

以下、参考にさせて頂いたサイトです。
ありがとうございました。

続きを読む

EC2+goofysでS3バケットをマウントしたい

概要

  • S3をファイルシステムとしてマウントしたいと思い試行錯誤したメモ。
  • 実現するためのOSSでは メジャーなもので s3fsgoofys があるっぽい。
  • goofys の方が評判が良いようなのでgoofysで検証。

目標

  • golangおよび gooyfs を導入
  • fstab で起動時に毎回マウントしたいので諸々の設定を整備

本家情報

goofysインストールの参考情報


作業環境

  • AWS + EC2(AmazonLinux) + S3

事前準備

1. マウント対象のS3バケットを新規作成。
2. 作成したバケットにアクセス可能となる IAMアカウントを用意
3. AmazonLinuxのEC2を作成し ec2-user または root でaws configure でIAMの設定を完了させておく
4. マウントポイントを先に作成しておく: 例) `mkdir -p /mnt/s3test`

参考サイトに従ってインストールを進めたが最新版goofysはそのままでは実装できなかった。とりあえずメモ残し。

バージョン確認

  • go versiongo version go1.7.5 linux/amd64 が入った

goofys インストール

  • go get github.com/kahing/goofys
  • バージョン確認: goofys --versiongoofys version 0.0.17-usemake build’ to fill version hash correctly`

マウント実行(エラー)

  • goofys <bucket> <mountpoint>

エラーメッセージ: goofys/internal/handles.go:456: undefined: url.PathUnescape
作者のkahing氏コメント: https://github.com/kahing/goofys/issues/196
go 1.8以上を入れてね、との事なのだが AmazonLinuxのリポジトリでは go 1.7.5 が入るので yumではNGか…

yumで導入したパッケージ一式を撤去

  • yum remove golang fuse -y

go lang本家からLinux用バイナリを取得して再度環境を構築

go lang のLinux用バイナリファイルを/usr/local/src へDL

  • cd /usr/local/src
  • wget https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz

go lang のバイナリファイルを展開

  • tar -C /usr/local -xzf /usr/local/src/go1.9.linux-amd64.tar.gz
  • ls -al /usr/local/go

go lang バイナリを展開した先へ path を通す

  • export GOROOT=/usr/local/go
  • export PATH=$PATH:$GOROOT/bin
  • env | grep -i go

${GOROOT} は goライブラリの設置されているパスを設定する環境変数らしい…
参考: GOROOT と GOPATH : “http://qiita.com/1000ch/items/e42e7c28cf7a7b798a02#goroot-%E3%81%A8-gopath

go lang のパスが通った事とバージョンを確認

  • go versiongo version go1.9 linux/amd64 が入った

goofysを再度インストール

  • export GOPATH=${HOME}/go
  • go get github.com/kahing/goofys
  • go install github.com/kahing/goofys

${GOPATH} は作業ディレクトリを指す環境変数らしい…
参考: “http://qiita.com/1000ch/items/e42e7c28cf7a7b798a02#goroot-%E3%81%A8-gopath

goofys 実行ファイルのあるディレクトリにもpathを通す

  • export PATH=${PATH}:${GOPATH}/bin

S3バケットをマウントしてみる

  • goofys <bucket> <mountpoint>

エラーが出る > main.FATAL Unable to mount file system, see syslog for details syslogを見てねとの事なので確認。
syslogには main.FATAL Mounting file system: Mount: mount: running fusermount: exec: "fusermount": executable file not found in $PATH#012#012stderr:#012 という事で fusemount コマンドが無い、というエラーのためマウントできないっぽい。

エラー対処

goofys本家のInstallation の項目で On Linux, install via pre-built binaries. You may also need to install fuse-utils first. って書いてた…
そういえば↑の手順の中で fuseパッケージを1回 yumで入れたものを消してたな…
fuse についてよく知らなかったので、go関連のパッケージであればgolangが関連パッケージで上書きインストールされてしまう?とも思ったがfuse パッケージ単体でOKだった
fuseとは : https://ja.wikipedia.org/wiki/Filesystem_in_Userspace
fuseとは : https://www.ibm.com/developerworks/jp/linux/library/l-fuse/index.html
libfuse公式 : https://github.com/libfuse/libfuse

goofys マウント実行

  • マウント: goofys <bucket> <mountpoint>
  • アンマウント : umount <mountpoint>

その他調整

  • DocumentRootに利用できるか
  • fstab で再起動後も設定反映させたい
  • 関連環境変数設定のまとめ
  • 再起動して確認

DocumentRootに利用できるか

  • mount <bucket> <mountpoint>/ -o allow_other,--uid=<uid>,--gid=<gid>
  • uid gid の確認 : id apache または id nginx などで

allow_otherオプション無しでマウントしていると apache/nginx の実行ユーザーから読めないっぽい…
allow_otherオプション+ uid gid はdaemon実行ユーザと同じ設定にしてマウントすれば動いた:

fstab で再起動後も設定反映させたい

  • /etc/fstab に設定を追記
  • <fullpath/to/>goofys#<bucket> <mountpoint> fuse _netdev,allow_other,--dir-mode=0755,--file-mode=0666,--uid=<uid>,--gid=<gid> 0 0

本家のUsage にfstab で使う場合は root にAWS credentials 設定をせよとあるのでrootのユーザ環境に設定。
fstab のサンプルもUsageにある : goofys#bucket /mnt/mountpoint fuse _netdev,allow_other,--file-mode=0666 0 0
その他参考: http://caters.works/2016/08/%E3%81%95%E3%81%8F%E3%82%89%E3%81%AEvps%E3%81%AB-goofys-%E3%81%A7-s3-%E3%82%92%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88/

fstab 設定の試行(再起動無しで)

  • sudo mount -a

fstabに記載したのと同じ<mountpoint>をマウントした状態で実行している場合は、umountした後に実行

aws credential の profile を指定したい場合(未検証)

  • goofys#<bucket> <mountpoint> fuse _netdev,allow_other,--file-mode=<filemode>,--profile=<profile> 0 0 っぽい

本家のissues でやり取りを発見 : https://github.com/kahing/goofys/issues/222

環境変数回りをまとめる

  • rootユーザの環境変数へ設定を追加 : /root/.bashrc
export GOROOT=/usr/local/go
export GOPATH=${HOME}/go
export PATH=${PATH}:${GOROOT}/bin:${GOPATH}/bin

システム再起動後にも各所の設定が反映される事を確認

環境変数 : sudo env | grep -i go
go lang : go version
goofys : goofys -v
fstabログ : sudo tail -f /var/log/messages | grep goofys : main.INFO File system has been successfully mounted.
マウント状況 : df -ah # s3の空き表示って 1ペタになるのか…

続きを読む

EC2が止まったらS3に「死にました」と投稿する

概要

EC2インスタンスのシャットダウンをトリガーに、何か処理を実行する方法を記します。
今回はS3に辞世の句を投稿していますが、AutoScalingでterminateされるEC2のlocalのsyslogをS3に転送する等の処理にも応用できます。

やり方

S3編

S3にバケットを作成する

death_poem_1_1.png
  ※S3のバケット名はFQDNにしておくと色々捗る(余談) 

IAM編

S3にフルアクセスできるIAMを作成する

death_poem_2_1.png

death_poem_2_2.png

death_poem_2_3.png

death_poem_2_4.png

death_poem_2_5.png

 ※アクセスキーIDとシークレットアクセスキーは後で使うのでメモ帳にコピペしてください 

EC2編

1.AWS CLIをインストールする

 ※Amazon Linuxの場合は不要です  

//pipのインストール
# curl -O https://bootstrap.pypa.io/get-pip.py

//Python を使用してスクリプトを実行
# python get-pip.py --user

//PATH 変数に実行可能パスを追加
# export PATH=~/.local/bin:$PATH
# source ~/.bash_profile

//pipが正しくインストールされたことを確認
# pip --version

//pip を使用して AWS CLI をインストール
# pip install awscli --upgrade --user

//AWS CLI が正しくインストールされたことを確認
# aws --version

2.AWS CLIの初期設定をする

# aws configure

AWS Access Key ID [None]: [アクセスキーID]
AWS Secret Access Key [None]: [シークレットアクセスキー]
Default region name [None]: [空でok]
Default output format [None]:[空でok]

3.辞世の句スクリプトを作成する

# vim /etc/rc.d/init.d/DeathPoem.sh
/etc/rc.d/init.d/DeathPoem.sh

#!/bin/sh

InstanceId=`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`

declare logdir=/var/www/log
declare log=/death_poem.log
declare bucket=files.hoge.com
declare now=`date +'%Y-%m-%d'`
declare s3_target="s3://${bucket}/death_poem_${now}/"

mkdir $logdir >/dev/null 2>&1

printf 'I am '$InstanceId'¥n' >> $logdir$log
printf 'I was born 'date --date=@$(expr `date +%s` - `cut -d "." -f 1 /proc/uptime`) >> $logdir$log
printf 'I was just 'cat /proc/uptime | awk '{print $1 / 60 /60 /24 "days (" $1 "sec)"}' |  tr -d '¥n''  from birth ''¥n' >> $logdir$log
printf 'I will die 'date'¥n¥n' >> $logdir$log

aws s3 sync $logdir $s3_target

4.辞世の句スクリプトに実行権限を与える

# chmod 755 /etc/rc.d/init.d/DeathPoem.sh

5.rc.local(起動時実行)を編集する

# vim /etc/rc.d/rc.local
/etc/rc.d/rc.local

#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local

/sbin/ethtool -s eth0 wol g

touch /var/lock/subsys/DeathPoem
ln -s /etc/init.d/DeathPoem.sh /etc/rc0.d/K00DeathPoem
ln -s /etc/init.d/DeathPoem.sh /etc/rc6.d/K00DeathPoem

6.EC2インスタンスを再起動する

# reboot

7.ログを確認する

# tailf /var/www/log/death_poem.log
/var/www/log/death_poem.log

I am i-************
I was born Mon Sep 11 04:00:48 UTC 2017
I was just 0.00817488days (706.31sec)  from birth 
I will die Mon Sep 11 04:12:34 UTC 2017

8.S3のバケットを確認する

上記と同じ内容の辞世の句が投稿されているはず!

余談

昔は簡単だった(らしい)

EC2で起動時やterminate時にシェルを実行する | Developers.IO

mytest.sh
#!/bin/sh
# chkconfig: 2345 99 10
# description: test shell

case "$1" in
 start)
       echo "start!" > /path/your/start.txt
       ;;
 stop)
       echo "stop!" > /path/your/stop.txt
       ;;
  *) break ;;
esac

あのクラスメソッドさんが書いた記事なら間違いない!
…と思っていたのですが、上記の方法では”start”のイベントしか取得できませんでした。
コメントにあったprocessnameを追加しても結果は同様でした。
原因をご存じの方いたらぜひ教えて欲しいです :bow:

とても参考になった文献

CentOS6.0 Shutdown時にコマンド実行 : 事象の水平線 :

リスペクト

心臓が止まったらSNSに「死にました」と投稿する

続きを読む

Deep Security User Night #5 レポート

こんにちは、ひろかずです。

Deep Security User Night #5に行ってきたので一筆書きます。
会場は前回に続きHAPON新宿
日本地図をモチーフにした机がオシャレな空間です。

プシュッと開けてからスタートです。

お品書き

  • Google Cloud Platformの紹介とセキュリティとDS on GCPの話
  • 我が家の箱入り娘を世間に晒すのは危険なのでDeep Securityに見守ってもらった話
  • Deep Security APIを活用したルールアップデートの自動化について

Google Cloud Platformの紹介とセキュリティとDS on GCPの話

Google Cloud Japan
金子さん

トレンドマイクロとは長い付き合い
よなよなエール好き(箱買い派)

2017/6にDeep SecurityがGCPに対応
AWSに比べてまだまだ機能は足りないけど、これから
評価ガイドあります!(スタートアップガイド)

  • IaaSにManagerを立てて、保護サーバーにAgentを入れる方式

GCPの概要
Google関連サービスは、10億を超えるユーザー
どういうインフラで動いてるの?

  • バカでかいデータセンター
  • データセンターから自作
  • ラックも自作
  • 国内サーバベンダーより多くのサーバを作ってる
  • コンポーネントを極限まで削っているので、セキュア
  • 管理は自動化。分散処理基盤がある。
  • データセンター as a Computer
  • その一部を切り出すのがGCP
  • 東京リージョン開設時は超忙しかった
  • インフラ規模のインフラ(プラネットスケールインフラストラクチャ)
  • 3年間で3兆円の投資

セキュリティ頑張ってます!

  • すべてのレイヤーに厳しいセキュリティが施されている
  • Googleのセキュリティ専任部門(750人)
  • FISCにも対応!
  • NICに特注チップを付けて、変なところにパケットを飛ばさないようにチェック
  • データは暗号化され、分割と難読化でディスク単位では読みだせないようにしている。
    データは、GCP内でやり取りされる(VMコピー時に、通信はインターネットに出ない)

HTTP(S)ロードバランサ

  • 1IPでリージョン跨ぎの負荷分散
  • 暖気申請なしで100万リクエストに対応

IaaSの特徴

  • ライブマイグレーション
  • 勝手にやってくれるクラウドサービスはGCPだけ。
  • KVMベースのハイパーバイザで対応している
  • 1000VMが5分で起動する
  • VMネットワークは16Gbps
  • GCPカスタムマシンタイプ(GPU特化型とか)

BigQuery

  • SaaSのようなデータウェアハウス
  • 72GBのフルスキャンが6.7秒
  • 大量サーバとストレージ、ペタビットネットワークによる超並列処理クエリ
  • 1TBを1秒でスキャンするために、10000ディスクスピンドルを用意する考え方

そろそろDeep Security

  • BigQueryでDSのログを検索(1.32TB, 40億レコード)
  • 4.2秒で検索!

我が家の箱入り娘を世間に晒すのは危険なのでDeep Securityに見守ってもらった話

フューチャーアーキテクト株式会社
日比野さん

Deep SecurityとElasticSearch

  • ハニーポットに対する通信をDSでの検知状況を可視化
  • パロアルトのファイアウォールも用意
  • ハニーポットはコンテナ
  • 今回は、サーバ型、低対話型のハニーポットを用意
  • 低対話型は、簡単だが取れる情報はそれなり

標的型攻撃の攻撃フェーズに対してハニーポットが取れる情報

  • 偵察
  • 情報探査
  • 情報集約
  • 情報送信

ハニーポット書籍

  • 実線サイバーセキュリティモニタリング
  • サイバー攻撃の足跡を分析するハニーポット観察日記

今回のハニーポットは、Open Canaryを採用(コンテナでデプロイ)

  • Dockerコンテナ実行環境を用意して、
  • ログはLogStashでElasticSearchに送信
  • ハニーポットのAWS適正利用規約がある(事業者によって異なるので注意)
  • Deep Securityは、10.1を使用
  • DSaaS環境を利用(今回は双方向通信を採用)
  • Deep Securityのログは、UDP514で通信させた

Paro Alto

  • 15日間は無料
  • ライセンス高いから、忘れずに消す!

ログの可視化には、ElasticStackがおすすめ!

  • Kibana
  • Elastich
  • X-Pack(有償サブスクリプション:レポーティング、アラート、グラフ、ML)
  • 最新版5.5で構築し、Syslog受信したログをElasticSearch

結果

  • 中国、台湾が大活躍
  • 8/31-9/3ごろまで
  • ピーク時6000件のログがでた(ParoAlto)
  • 平日は、MSSQL(1433)が多かったが、土日はSSHが爆裂
  • 攻撃で使われるアカウントやパスワードは、リストに載っているものがやはり多かった(デフォルトパスワードは、やはり危険)
  • 機械学習は、閾値を設けた使い方が有効そう
  • Deep Securityのログの正規化は簡単にできた。
  • ファイアウォール機能で遮断していたので、侵入防御やセキュリティログ監視は反応しなかった。

まとめ

  • ログは事実だが、それだけでは意味がある分析結果は得られない
  • とりあえずログを集めるのはNG
  • 何をなすためにログを集めるのかという必要性を理解して、小さく始めるのがいい
  • ログの結果から対策に活かすPDCAが大事!

Deep Security APIを活用したルールアップデートの自動化について

アイレット株式会社
恩田さん

Deep Securityは、securitypackというサービスで使っている。
ルールアップデートしてますか?

  • 手動や自動で適用してもいいけど、いきなり防御されたら大変
  • アップデート処理は、アクセスが集中するので処理が重くて大変!

DeepSecurity API

  • REST
  • Sorp

REST API

  • 機能がだいぶ足りない
  • 新しい機能が実装されている

SOAP API

  • 古典的な機能が実装されている
  • 機能の数は多数

SDK

  • 統一されたフロントを提供するので区別しなくていい(機能の60%)
  • dsm.py:マネージャークラス
  • polices.py:ポリシークラス, ルールクラス
  • が、GitHubプロジェクトは全然動いてない
  • なので、足りない部分は実装しよう!

まとめ

  • SOAPが解決してくれる

要望

  • アップデート配信日はサーバーを強化してください!
  • APIとUIのドメインを統一してほしい

今回も盛り上がった回でした!

今日はここまでです。
お疲れさまでした。

続きを読む

CloudWatchカスタムメトリクス追加方法(ディスク、メモリ)

やること

AWSのCloudWatchは、デフォルトだとディスクとメモリ使用率を見ることができません。
そこで利用するのがカスタムメトリクスです。
ディスクとメモリのカスタムメトリクスを追加します。

前提

  • インスタンスに、EC2FullAccessとCloudWatchFullAccessの権限を持ったIAMロールを関連づけている。(推奨)
  • もしくは、上記権限を持ったユーザがおり、アクセスキーとシークレットキーを用意している。(非推奨)

手順

1.対象サーバへログインする

2.スクリプトのインストール

# yum install perl-Switch perl-DateTime perl-Sys-Syslog perl-LWP-Protocol-https perl-Digest-SHA
# curl http://aws-cloudwatch.s3.amazonaws.com/downloads/CloudWatchMonitoringScripts-1.2.1.zip -O

3.圧縮ファイルの解凍と圧縮ファイルの削除

# unzip CloudWatchMonitoringScripts-1.2.1.zip
# rm CloudWatchMonitoringScripts-1.2.1.zip

4.crontabへのスクリプト実行設定

# crontab -e

 以下の内容を記述する。
 今回は、5分に1回の実行に設定する。

●IAMロール適用バージョン(推奨)
*/5 * * * * ~/aws-scripts-mon/mon-put-instance-data.pl  --mem-util --disk-space-util --disk-path=/ --from-cron

●アクセスキーとシークレットキーを記載(非推奨)
*/5 * * * * ~/aws-scripts-mon/mon-put-instance-data.pl  --mem-util --disk-space-util --disk-path=/ --from-cron --aws-access-key-id=アクセスキー --aws-secret-key=シークレットキー

5.crondの再起動

# systemctl restart crond

6.追加の確認
 AWSのコンソール画面で、CloudWatchを選択し、DiskSpaceUtilizationが追加されていることを確認する。

参考URL
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/mon-scripts.html#using_put_script

続きを読む

IoTでペットヘルスケア[実装編:センサデータの取得~AWS IoT連携]

本記事で取り扱うこと

IoTでペットヘルスケア[構想編]
IoTでペットヘルスケア[実装編:センサデータの取得~AWS IoT連携] ←本記事(構想編の続編)です
IoTでペットヘルスケア[実装編:AWSサービス間の連携]←次回です

IoTでペットヘルスケア[構想編]で紹介したアーキテクチャのうち、下記の赤枠内の実装(センサデータの取得からAWS IoTへデータを取り込むまでの部分)について、順を追って説明します。手順は現時点(2017/5)のものであるため、時間が経過している場合には、より便利なものが出ている可能性もありますので最新の情報をご確認ください。

AWS Design_pets_healthcare_実装編01.png

Raspberry Pi 3の準備

利用機材

以下を準備します。

  • Raspberry Pi3 Model B
  • GrovePi+
  • Grove – PIR Motion Sensor
  • microSDカード(今回は16GB)
  • USB Micro B(給電用)
  • その他(セットアップ用)
    • HDMIケーブル
    • モニタ(HDMI対応)
    • USB接続マウス
    • USB接続キーボード
    • microSDカード・リーダライタ

その他以外を接続すると、以下のような状態になります。
raspberrypi.jpg

OSのインストール

RASPBIAN JESSIE WITH PIXELをインストールします。
シンプルにインストールするだけですが、以下のとおりです。

  1. RASPBIAN JESSIE WITH PIXELをダウンロード、解凍します。

    • 今回は以下のリリースを利用。

      • Version:April 2017
      • Release date:2017-04-10
      • Kernel version:4.4
  2. 解答したイメージファイルをmicroSDカードへDDで書き込む。

  3. microSDカードをRaspberry Piへ挿入、セットアップ用のその他機器を接続して給電用USBケーブルを接続する。

[オプション] 日本語環境を設定

初期インストール時は英語環境になっているため、必要であれば日本語環境化しましょう。

  1. OS起動後、MenuからRaspberry Pie Configurationをクリックします。

    • このあたりのメニューの名前はOSのバージョンで少しずつ変わっていますので、それらしいものをお探しください。
  2. Localisationのタブを選択し、以下を設定する。(※設定後、再起動を促されますが、Noを選択する)

    • Locale

      • Language: ja(Japanese)
      • Country: JP(Japan)
      • Character Set: UTF-8
    • Time zone
      • Area: Japan
    • Keyboard
      • Country: Japan
      • Variant: Japanese
  3. 日本語フォントをインストールする。
    • ログイン時のデフォルトユーザ(pi)でターミナルを開き、下記を実行します。
$ sudo apt-get install jfbterm

上記の手順を実行後、OSを再起動すれば日本語化されます。

[オプション] エディタのインストール

好みの問題かもしれませんが、初期のviは使いづらいためvimをインストールします。

$ sudo apt-get update
$ sudo apt-get install vim

[オプション] tmpのRAMDISK化

長期間の稼働を前提とするため、念のためにファイルI/Oが継続的に行われる部分についてはRAMDISK化しておきます。(そんなに大量に読み書きするわけではないため、気休め程度ですが。)

/etc/fstabに以下を追記し、再起動後に正常にマウントされていることを確認します。

tmpfs           /tmp            tmpfs   defaults,size=32m 0       0
tmpfs           /var/tmp        tmpfs   defaults,size=16m 0       0
tmpfs           /var/log        tmpfs   defaults,size=32m 0       0

ネットワーク接続

Raspberry Piをインターネットへ接続できるように設定します。
有線でも無線でも、お好きなほうでOKですが、設置場所を考えると無線のほうが取り回しが楽なのでいいかもしれません。GUIからも設定できるため、特に難しいことはないので説明は割愛します。

GrovePiのライブラリをインストール

GrovePiとセンサーを利用するためのライブラリを導入します。

  • GrovePiのライブラリをクローンしてインストール
$ mkdir /home/pi/grovepi
$ cd /home/pi/grovepi
$ sudo git clone https://github.com/DexterInd/GrovePi
$ cd /home/pi/grovepi/GrovePi/Script
$ sudo chmod +x install.sh
$ sudo ./install.sh

途中でインストールの継続を聞かれるのでY(Yes)を選択。

  • GrovePi+の接続確認。
    GrovePi+が接続されている状態で、以下のコマンドを実行します。
$ sudo i2cdetect -y 1

WS000004.JPG
上記のように04が見えていれば、正常にGrovepi+を認識できています。

PIR Motion Sensorのデータ取得

  • PIR Motion SensorをD8ポートに接続します。

接続ポートは他でもOKですが、次項のサンプルコードではD8前提となっています。

  • センサデータ取得のサンプルコード

0.5秒毎にセンサデータの取得(動きを検知)して、1分ごとの集計結果をファイルに出力するサンプルです。デーモン化する前提ですので、不要な部分はコメントアウトしてあります。動作確認時はコメントアウトを外してprint部分をすれば、0.5秒おきの検知結果を標準出力へ出します。
#長期間運用する場合は、デーモン化してしまうのでsyslogが溢れないようにコメントアウトしたままのほうが良いと思います。

grove_pir_motion_sensor_d.py
import os
import sys
import time
import grovepi
import datetime

def getMotionSensor():
    pir_sensor = 8
    motion=0
    grovepi.pinMode(pir_sensor,"INPUT")
    countPerMin = 0
    i = 0

    while True:
        try:
            if i >= 120:
                d = datetime.datetime.today()
                output = "{ " + "\"countPerMin\":" + str(countPerMin)    + ",\"timestamp\":\"" +d.strftime("%Y-%m-%dT%H:%M+09:00")+ "\" }"
                print output

                f = open('/tmp/motionDetected.json', 'w')
                f.write(output)
                f.close()                     

                i = 0
                countPerMin = 0
            else:
                i = i + 1

        # Sense motion, usually human, within the target range
            motion=grovepi.digitalRead(pir_sensor)
            if motion==0 or motion==1:  # check if reads were 0 or 1 it can be 255 also because of IO Errors so remove those values
                if motion==1:
                    countPerMin += 1
                    #print ('Motion Detected')
                    #print i
                #else:
                    #print ('-')
            time.sleep(.5)

        except IOError:
            print ("Error")

def fork():
        pid = os.fork()

        if pid > 0:
                f = open('/var/run/motionsensor.pid','w')
                f.write(str(pid)+"\n")
                f.close()
                sys.exit()

        if pid == 0:
                getMotionSensor()


if __name__=='__main__': 
    fork()   
  • テスト実行

    • ハードウェアのPinを読みに行くため、root権限での実行が必要です。
    • うまく動作すれば、/tmp/motionDetected.jsonへ以下のような内容が出力されます。
{ "countPerMin":0,"timestamp":"2017-05-05T14:47+09:00" }

こちらのセンサーは、もう少し短い間隔でデータを取得することも可能ですが、ネコ様がトイレに入って出てくるまでの動きを検知したいので、0.5秒おきに検知を行い、1分間(最大120回検知)の中でどのくらい動きがあったのかを返すためのデータ出力を行っています。最終的には、この検知回数を元に通知を行っていきます。

純粋にセンサーしかない場合はともかくとして、ラズパイやEdisonなどの処理能力があるものであれば、センサーの生データそのものよりも、少し加工して使いやすくしたものを作り出したほうがよいのでは、と思います。

データ取得スクリプトのデーモン化

データ取得のスクリプトをOS起動時やプロセス停止時に自動で起動するため、デーモン化します。
#最近はinit.dから変わっていますので、久しぶりに触る方はご注意ください。・・・一瞬、困ったのは私だけ?w

  • 上記のgrove_pir_motion_sensor_d.pyを/usr/local/libに配置します。
  • /etc/systemd/system に motionsensord.serviceを作成します。
motionsensord.service
[Unit]
Description=Pir Motion Sensor Daemon
[Service]
ExecStart=/usr/local/lib/grove_pir_motion_sensor_d.py
Restart=always
Type=forking
PIDFile=/var/run/motionsensor.pid
[Install]
WantedBy=multi-user.target
  • サービス設定を読み込み、手動でデーモンを起動して動作確認します。

設定のリロード、手動起動

$ sudo systemctl daemon-reload
$ sudo systemctl start motionsensord

デーモンのステータスやファイル出力を確認

$ sudo systemctl status motionsensord

出力結果の例
WS000005.JPG

  • 自動起動を有効にします。
$ sudo systemctl enable motionsensord

OS再起動後も動作していれば正常に設定できています。

AWS IoTの準備

1年ほど前と比較して、非常に簡単になっています。基本はAWS IoTに示されれる手順に沿って実行するだけです。
#この説明いらないのでは・・・と思いつつ、AWS IoT初学者の方もいらっしゃると思いますので、一通りの流れを紹介ということで。

デバイスの登録

Raspberry PiをAWSに接続するための設定を行います。

connect_01.jpg
AMCからAWS IoTのコンソールを開き、Connect のページを選択します。Configuring a deviceの”Get started”を選択します。

WS000008.JPG
接続までの流れ説明ページが表示されるので、”Get started”を選択します。

WS000009.JPG
環境に応じてSDKをセットアップするためのKitが準備されていますので、利用したいものを選択します。今回はRaspbianなのでLinux/OSXを選択します。また、AWS IoTへの部分を記述する際の言語も選択します。今回はNode.jsを利用します。最後に”Next”を選択します。

WS000010.JPG
AWS IoT上で管理するデバイスの名前を指定します。こちらは、管理者がどのデバイス(今回だとRaspberry Piのどのマシンなのか)が分かればよいため、任意の名前でOKです。指定後、”Next step”を選択
※どこかに設定した値と一致させなければならない、といったことはありません。

WS000011.JPG
接続用Kitのダウンロードを行います。内容を確認後、”Next step”を選択します。

ちなみに、ここまでの操作により、以下の手順が自動的に行われています。(1年前は個別に行う手順でしたので関係が分かりやすかったのですが、今は便利になった反面、何が行われているのか少々分かり難くなっています。)

  • デバイスがAWS IoTにデータを送る権限の定義(ポリシー作成)

    • A policy to send and receive messages の部分です
  • デバイスに配置する認証キーの作成
    • A certificate and private key の部分です。Kitに含めれます。
  • デバイスと認証キー、ポリシーの関連付け
    • 3つを関連付け、特定のデバイスに権利を持たせて認証し、AWS IoTのトピック(MQTTでつかうチャネルのようなもの)へPublish/Subscribeできる状態とします。

デバイスの設定

WS000012.JPG
ダウンロードしたファイルをデバイス(Raspberry Pi)へ転送し、示されている手順を実行します。
※こちらの画面は接続確認にそのまま利用しますので、開いたままにしておいてください。

start.shを実行すると、以下の処理が実行されます。

  • 証明書の確認
  • aws-iot-device-sdkをはじめ、各種モジュールのインストール
  • AWS IoTとの接続テスト
start.sh
start.sh
# stop script on error
set -e

# Check to see if root CA file exists, download if not
if [ ! -f ./root-CA.crt ]; then
  printf "\nDownloading AWS IoT Root CA certificate from Symantec...\n"
  curl https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem > root-CA.crt
fi

# install AWS Device SDK for NodeJS if not already installed
if [ ! -d ./node_modules ]; then
  printf "\nInstalling AWS SDK...\n"
  npm install aws-iot-device-sdk
fi

# run pub/sub sample app using certificates downloaded in package
printf "\nRuning pub/sub sample application...\n"
node node_modules/aws-iot-device-sdk/examples/device-example.js --host-name=av8pngo3zyi88.iot.ap-northeast-1.amazonaws.com --private-key=y_raspberrypi_01.private.key --client-certificate=y_raspberrypi_01.cert.pem --ca-certificate=root-CA.crtroot@raspberrypi:~/workspace/cat_healthcare/gitrepo/connect_device_package#

デバイスに接続し、start.shを実行すると、下記のようにconnectと表示されれば接続が成功しています。
WS000015.JPG

さきほどのAWS IoTの画面に戻ると、Step3の下に、デバイスからPublishされたメッセージが届いているのが分かります。これはstart.shから実行された接続確認用のコードから送られています。
WS000013.JPG

続いて、Step 4のテキストボックスに任意の文字列を入れ、Send messageを選択すると、デバイス(Raspberry Pi)側がSubscribeしているTopicにPublishすることができます。今回はテスト用に”test publish message AAAA”と送ってみました。すると、以下のようにデバイス側で受信することができています。
WS000014.JPG

以上でデバイス側の設定は完了です。

センサデータ取得の連携設定

取得したセンサーのデータを実際にAWS IoTへ送信します。

AWS IoTへのPublish

サンプルですので、べた書き部分が多いですが、以下のようなスクリプトを作成します。今回はconnect_device_packageを利用しやすくるため、同ディレクトリに配置してしまいます。

publish_data_pirmotion.js
var awsIot = require('aws-iot-device-sdk');
var fs = require('fs');

var clientId_suffix = 'MotionSensor01';
var deviceId = 'y_raspberrypi_01';
var sensor = 'motion_sensor';
var MQTT_clientID = deviceId + "_" + clientId_suffix;

var topicname = "topic/" + deviceId + "/sensor/" + sensor;

var device = awsIot.device({
    keyPath: 'y_raspberrypi_01.private.key',
    certPath: 'y_raspberrypi_01.cert.pem',
    caPath: 'root-CA.crt',
    clientId: MQTT_clientID,
    region: 'ap-northeast-1'
});


device.on('connect', function () {

    console.log('connect');
    setInterval(function () {

        var messageJson = JSON.parse(fs.readFileSync('/tmp/motionDetected.json', 'utf8'));
        messageJson.clientId = MQTT_clientID;
        messageJson.deviceId = deviceId;

        var message = JSON.stringify(messageJson);

        console.log("Publishing.. " + topicname);
        device.publish(topicname, message);
    }, 60000);
});

処理としては、モーションセンサのデータ取得結果(motionDetected.json)に対してdeviceIdやclientIdをデータとして含め、1分毎にAWS IoTへ送信しているだけになります。

注意点としては、MQTTの仕様として、同一のトピックに対して接続する際にclientIdごとにコネクションを確立しており、複数のデバイス間で重複していると、セッションの奪い合い(お互いに接続、接続断を繰り返してしまう)ことになります。そのため、一意になるようにclientIdを生成して上げる必要があります。今回はデバイスの名前にセンサーの名前を組み合わせて生成しました。

MQTTのトピック名についても、センサーの種類ごとに分けて生成していく想定になっています。センサーの種類を増やす際には、これらのスクリプトやトピックごと増やすアーキテクチャとなります。AWS IoT側からデータを利用する際は、このトピック名を対象として選択することになるため、複数種類のデータを取り扱う際には、十分に検討して設計が必要です。例えば、途中のパスによって値のグルーピングを行う等。

動作確認は、AWS IoTのテスト用コンソールを利用します。
WS000017.JPG
AWS Iotの”Test”ページを開き、トピック名を入力して”Subscribe to topic”を選択します。

すると、Subscriptionsの中に指定したトピック名が表示されますので、選択します。1分毎にデータを送っているため、成功していれば、以下のようにメッセージを受信(Subscribe)できます。
WS000019.JPG

以上でモーションセンサーで実際に取得したデータをAWS IoTへ連携することができるようになりました。

Publish用スクリプトのデーモン化

publish_data_pirmotion.jsもセンサデータ取得用のスクリプトと同様にデーモン化してしまいます。昔はnode.jsだとforeverなどを利用していた気がしますが、systemdの場合は、Pythonスクリプトと同様でOKです。

  • /etc/systemd/system に publish_motionData.serviceを作成します。
publish_motionData.service
[Unit]
Description=publish motion data
After=syslog.target network.target
[Service]
Type=simple
ExecStart=/root/.nvm/versions/node/v6.1.0/bin/node  publish_data_pirmotion.js
WorkingDirectory=/root/workspace/cat_healthcare/gitrepo/connect_device_package
KillMode=process
Restart=always
[Install]
WantedBy=multi-user.target

#rootで作成しちゃっていますので、上記のような内容になっています。

  • サービス設定を読み込み、手動でデーモンを起動して動作確認します。

設定のリロード、手動起動

$ sudo systemctl daemon-reload
$ sudo systemctl start publish_motionData

デーモンのステータスやファイル出力を確認

$ sudo systemctl status publish_motionData
  • 自動起動を有効にします。
$ sudo systemctl enable publish_motionData 

OS再起動後もAWS IoTへデータが送られていることを確認できればOKです。

センサーの設置

Cat’s Restroomにセンサーを設置します。

我が家では、以下のように個室風のトイレが設置されています。
Cat'sRestroom_外観.jpg
少々わかりにくいのですが、上記の写真のようになっています。2つのカラーボックスを棚として開いている側を向き合わせて置き、片方の背板を出入り口用に切り取っています。中は市販のネコトイレ(すのこ式)です。
#ホワイトペレット最高!

その中に対して、以下のようにセンサーを設置しました。図は真横から見たときの断面図・・・絵心なさすぎてすいません。
RestroomSensor01.png

入り口の背板(上部)の内側にモーションセンサーを取り付け、トイレ内部の動きを検知するようにしています。配線は2つのカラーボックスの間から通して、ネコ様の出入りの邪魔にならないようにしています。また、トイレ内の上部を中心に検知する配置のため、トイレ掃除等を誤検知しにくく・・・もなっているはずです。
#誤検知については、AWS IoT側で受け取ったデータを処理する際に条件付け(しきい値判定など)を行うことでも回避しています。

この配置で、0.5秒に一回のセンサーデータ取得を行うと、ネコ様がトイレに入るとバッチリ検知ができるようになりました。

おわりに

今回、センサデータを取得してAWS IoTへデータ連携を行うところまでできるようになりました。次回はAWS IoT側で受け取ったデータを活用して召使いへ連携していく部分について記載したいと思います。

また、他にもセンサーはあるのですが、長くなりすぎるため別立てして拡張編でも書いたほうがよさそうです。

次回:IoTでペットヘルスケア[実装編:AWSサービス間の連携]
cat01.jpg

続きを読む

JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (2)

この記事では、複数のDockerコンテナで構成される環境を構築するための設定例を具体的に解説します。ワークショップのフォローアップではありますが、一般的な事柄を扱いますので、ワークショップに参加されていない方にもお読み頂ける内容です。

前回の記事: JAWS DAYS 2017 ワークショップ Docker on Elastic Beanstalk 〜Days after tomorrow〜 (1)

春ですね

こんにちは。Emotion Techの子安です。ようやく暖かくなってきましたね。やっぱり春といえば桜ですかね!

sakura.jpg

恐縮です。

前回のつづき

さて、前回はテーマとする環境の構成を説明し、利用するコンテナ定義ファイル docker-compose.yml の枠組みを解説しました。更に web app db コンテナのうち、 web db コンテナの設定を見ました。今回はアプリケーションそのものである app コンテナを見ていきます。

各コンテナの設定 2

appコンテナ

app:
  image: phusion/passenger-ruby23:0.9.20
  environment:
    APPLICATION_ENV: development
    APPLICATION_ROLE: app
    PASSENGER_APP_ENV: development
  labels:
    eb.workshop.role: app
  networks:
    - eb.workshop
  depends_on:
    - db
  volumes:
    - ./app/init/40_setup.sh:/etc/my_init.d/40_setup.sh:ro
    - ./app/passenger/passenger.conf:/etc/nginx/sites-available/default:ro
    - ./app/rails-app:/var/www/rails-app

Ruby on Railsのアプリを実行するappコンテナです。phusion/passenger-dockerイメージを利用しています。このイメージはさまざまな機能を持っていますが、まずは設定内容を見ましょう。

  • image: phusion/passenger-dockerイメージを指定しています。
  • environment: イメージ側で PASSENGER_APP_ENV 環境変数を読んでRailsの環境を切り替えてくれる機能があります。
  • volumes: ここで3つのマウントを指定しています。コンテナ側のパスを見てください。

    • /etc/my_init.d/40_setup.sh:ro: /etc/my_init.d 配下に置いたスクリプトを初期化時に実行してくれる機能があります。実行権限を忘れずにつけるようにします。 :ro は読み取り専用の意味です。
    • /etc/nginx/sites-available/default:ro: ここにnginxの設定ファイルが置かれています。
    • /var/www/rails-app: Railsのアプリケーションをここに置きます。これは任意の場所で構いません。上記のnginx設定ファイルに記述しています。

phusion/passenger-docker

phusion/passenger-dockerは、Phusion社がメンテナンスしているDockerイメージです。このイメージは、さらに別のイメージ phusion/baseimage-docker をベースとして作られています。このphusion/baseimage-dockerには、いくつかの有用な機能が含まれています。

  • 正しいinitプロセス: /sbin/my_init にinitプロセスを内蔵しています。本来initプロセスには、親プロセスのいなくなった子プロセス(孤児)を里親として引き受ける役割があります。加えて SIGTERM を受け取った際に、他のサービスを終了させる機能を持ちます。
  • スーパーバイザ: 軽量なスーパーバイザ runit を内蔵しています。
  • ログマネージャ: syslogデーモン syslog-ng を内蔵しています。logrotateも設定済みです。
  • cron: cronデーモンを内蔵しています。

重要なことは、これらの機能が協調動作するように設定されていることです。 /sbin/my_init がrunitを立ち上げ、runitがsyslog-ngやcronを管理します。デフォルトのコンテナ起動コマンドが /sbin/my_init に設定されていますので、

docker run phusion/baseimage

として起動することで、最小限のコンテナ化されたLinux(Ubuntu)環境を手に入れることができます。他にも、今回も利用している /etc/my_init.d 配下のスクリプトを初期化時に実行する機能、root以外でコマンドを実行するための setuser コマンドなど、様々な機能や仕組みを備えています。詳しくはWebページを参照ください。

phusion/passenger-dockerは、このphusion/baseimage-dockerの上に、nginxやrubyのアプリケーションサーバである Phusion Passenger をインストールしてあります。nginxはrunitの配下に設定されていますので、すぐにスーパーバイズされたデーモンとして稼働させることができます。

コンテナ初期化スクリプト

可能なことはコンテナ自身に判断させるのが良い設計です。そのためにコンテナ起動時に実行するスクリプトを組み込むことがよく行われます。今回 app コンテナで設定している初期化スクリプトの内容を見てみましょう。 /app/init/40_setup.sh を参照ください。

このスクリプトの主な役割は4つです。

  • nginxを起動可能にする
  • Railsアプリのセットアップ
  • 環境変数の引き渡し
  • DBマイグレーション

ハンズオンのため、本来はコンテナ初期化スクリプトに記述すべきでない処理もここに入っていますが、続けて詳しく説明します。

/app/init/40_setup.sh
rm -f /etc/service/nginx/down

これはrunitの機能です。サービスを定義するディレクトリ(今回は /etc/service/nginx )の直下に down という名前のファイルがあると、自動起動しません。phusion/passenger-dockerイメージ側で配置されているものですが、自動起動してほしいのでファイルを削除しています。

/app/init/40_setup.sh
chown -R app:app /var/www
cd /var/www/rails-app
setuser app bundle install --path=../vendor/bundle
if [ "$PASSENGER_APP_ENV" = 'production' ]; then
  setuser app bin/rails assets:precompile
  setuser app bundle install --path=../vendor/bundle --without test development --deployment --clean
fi

このあたりは実環境であればCIで、ビルドの一処理として適切に実施すべきものです。アプリケーションのファイルパーミッションを適切に設定し、ライブラリのインストールを行います。更にproductionモードであれば、アセットのプリコンパイルと不要なライブラリの削除を行います。

/app/init/40_setup.sh
echo "passenger_env_var 'SECRET_KEY_BASE' '$SECRET_KEY_BASE';" >> /etc/nginx/conf.d/10_setenv.conf
echo "passenger_env_var 'RDS_HOSTNAME' '$RDS_HOSTNAME';" >> /etc/nginx/conf.d/10_setenv.conf

ここは環境変数をpassengerへ引き渡すための処理です。コンテナでは環境変数を扱う機会が多くなりますので、利用するミドルウェアに合わせて設定が必要です。

/app/init/40_setup.sh
RAILS_ENV=$PASSENGER_APP_ENV setuser app bin/rails db:create db:migrate

こちらはDBのマイグレーションです。この処理も実環境であればコンテナ初期化スクリプトではなく、デプロイの手順の一貫として実施すべきものです。

次回へ

ここまでが app コンテナの設定でした。長くなりましたので、つづきは次回にしたいと思います。次回はElastic Beanstalkの設定ファイル、Dockerrun.aws.jsonを解説する予定です。

続きを読む

[AWS] EC2(RHEL7.3)+postfix+Dovecot+Letsencryptでメールサーバを構築

インフラ勉強中の者がAWSに以下の環境を構築するまでの流れを実施したので、備忘録として残しておこうと思います。

開発環境

RedHatEnterpriseLinux 7.3
postfix 2.10.1
Dovecot 2.2.10
Thunderbird 45.8.0
webサーバのドメイン : example.com
webサーバのIP : X.X.X.X
メールサーバのドメイン : mail.example.com
メールサーバのIP : Y.Y.Y.Y
※webサーバとメールサーバを分けたサーバ設計です。

実施事項

◎構築前の確認/下準備
◎インストール
◎Let’s Encryptの設定
◎postfixの設定
◎Dovecotの設定
◎Thunderbirdの設定

構築前の確認/下準備

1.Route53にてDNSの設定を確認

Name Type Value
example.com MX 10 mail.example.com.
mail.example.com A Y.Y.Y.Y

dig mail.example.com mx等で設定通りの値が返ってくるか確認

[ec2-user@ip-10-0-200-67 ~]$ dig mail.example.com mx
~略~
;; AUTHORITY SECTION:
example.com.    60  IN  SOA ns-ZZZZ.awsdns-ZZZZ.com. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400

2.ポートの設定を開放・確認
メールの送受信で使用する下記のポートが、(1)EC2のセキュリティグループ(2)サーバでそれぞれ開放されているか確認
・SMPT(25) ・SMTPS(465) ・IMAPS(993) ・POP3S(995)

(1)EC2のセキュリティグループでの確認
mail1.png

(2)サーバでの開放・確認

[ec2-user@ip-10-0-200-67 ~]$ sudo su - 
[root@ip-10-0-200-67 ~]# yum install firewalld   //firewalldのインストール
[root@ip-10-0-200-67 ~]# systemctl start firewalld.service   //firewalldのサービスを起動
[root@ip-10-0-200-67 ~]# firewall-cmd --add-port=25/tcp --permanent
success
[root@ip-10-0-200-67 ~]# firewall-cmd --add-port=465/tcp --permanent
success
[root@ip-10-0-200-67 ~]# firewall-cmd --add-port=993/tcp --permanent
success
[root@ip-10-0-200-67 ~]# firewall-cmd --add-port=995/tcp --permanent
success
[root@ip-10-0-200-67 ~]# firewall-cmd --reload 
[root@ip-10-0-200-67 ~]# firewall-cmd --list-ports   //開放されているポート一覧の確認
465/tcp 80/tcp 443/tcp 995/tcp 25/tcp 993/tcp

インストール

基本パッケージのインストール
[root@ip-10-0-200-67 ~]# yum -y groupinstall base
[root@ip-10-0-200-67 ~]# yum -y groupinstall development
[root@ip-10-0-200-67 ~]# yum -y groupinstall network-tools
Dovecotのインストール
[root@ip-10-0-200-67 ~]# yum -y install dovecot

postfixはデフォルトでインストールされています。

Let’s Encryptの設定

Let’s Encrypt のクライアントソフトをインストール

[root@ip-10-0-200-67 ~]# cd /usr/local/
[root@ip-10-0-200-67 ~]# git clone https://github.com/letsencrypt/letsencrypt
[root@ip-10-0-200-67 ~]# cd letsencrypt/
[root@ip-10-0-200-67 ~]# ./letsencrypt-auto --help

Let’s Encrypt クライアントの待ち受けポートの開放

[root@ip-10-0-200-67 ~]# firewall-cmd --add-port=80/tcp --permanent
[root@ip-10-0-200-67 ~]# firewall-cmd --add-port=443/tcp --permanent
[root@ip-10-0-200-67 ~]# firewall-cmd --reload

Let’s Encrypt のSSL/TLS証明書を取得
今回はメールサーバを独立させたサーバ設計なのでstandaloneとなります。
apache,Nginx等のwebサーバと同じサーバでメールサーバを設定する場合は、バーチャルホストの設定等が必要になります。(今回は省略)

[root@ip-10-0-200-67 ~]# ./letsencrypt-auto certonly --standalone 
> -d mail.example.com 
> -m myname@example.com  ←任意のメールアドレスで良い
> --agree-tos

Let’s EncryptのSSL/TLS証明書は90日間で有効期限が切れるので、毎月1日の朝5時にcronで証明書の自動更新とpostfix・Devocotのリロードを実施します。

[root@ip-10-0-200-67 ~]# crontab -e
========== crontabのファイル =============
00 05 01 * * /usr/local/letsencrypt/letsencrypt-auto certonly --standalone -d mail.example.com --renew-by-default && /bin/systemctl reload postfix && /bin/systemctl reload dovecot
==========================================

postfixの設定

Postfixの設定ファイルを編集

[root@ip-10-0-200-67 ~]# cp -ip /etc/postfix/main.cf /etc/postfix/main.cf.org  //元のファイルをバックアップ
[root@ip-10-0-200-67 ~]# vi /etc/postfix/main.cf

========== main.cfのファイル =============
# このメールサーバのホスト名(FQDN)を指定
#myhostname = host.domain.tld
 ↓
myhostname = mail.example.com

# このメールサーバのドメイン名を指定
#mydomain = domain.tld
 ↓
mydomain = example.com

# メールアドレスを「ユーザ名@ドメイン名」の形式にする
#myorigin = $mydomain
 ↓
myorigin = $mydomain

# 全てのホストからメールを受信する
inet_interfaces = localhost
 ↓
inet_interfaces = all

# mydomain = で指定したドメイン宛のメールを受信する
mydestination = $myhostname, localhost.$mydomain, localhost
 ↓
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

# 存在しないメールアドレス(ユーザ)宛のメールの受信を拒否する
#local_recipient_maps = proxy:unix:passwd.byname $alias_maps
 ↓
local_recipient_maps = proxy:unix:passwd.byname $alias_maps

# 中継を許可する宛先ドメインを指定
#relay_domains = $mydestination
 ↓
relay_domains = $mydestination

# 転送サーバを使わない
#relayhost = [an.ip.add.ress]
 ↓
relayhost =

# メールの格納フォーマットの指定
#home_mailbox = Maildir/
 ↓
home_mailbox = Maildir/

# 不要な情報を公開しない
#smtpd_banner = $myhostname ESMTP $mail_name
 ↓
smtpd_banner = $myhostname ESMTP


---(下記を最終行に追加)---------------------------
# VRFYコマンドの使用を禁止する
disable_vrfy_command = yes

# MailBoxの最大サイズの指定
# メールボックスがMaildir形式のためこの設定は無視されますが「message_size_limit」との兼ね合いのため設定しておきます
mailbox_size_limit = 204800000

# 受信メールサイズの制限「mailbox_size_limit」より少ない値を設定してください
message_size_limit = 5120000

# 接続元の制限(スパムメール対策)
smtpd_client_restrictions =
    check_client_access hash:/etc/postfix/access
    reject_rbl_client zen.spamhaus.org
    reject_rbl_client all.rbl.jp
    reject_non_fqdn_sender
    reject_unknown_sender_domain

# エンベロープアドレス(MAIL FROM)による制限(スパムメール対策)
smtpd_sender_restrictions =
    reject_rhsbl_sender zen.spamhaus.org
    reject_unknown_sender_domain


########## SMTP-Auth関連 ##########
# SASL認証を有効化
smtpd_sasl_auth_enable = yes

# Dovecot SASL ライブラリを指定
smtpd_sasl_type = dovecot

# Dovecot SASL ライブラリの認証ソケットファイル /var/spool/postfix/ からの相対パスで記述
smtpd_sasl_path = private/auth

# 古いバージョンの AUTH コマンド (RFC 2554) を実装した SMTP クライアントとの相互運用性を有効にする
broken_sasl_auth_clients = yes


########## TLS/SSL関連 ##########
# TLSを有効化
smtpd_use_tls = yes

# 宛先のメールサーバがTLSに対応していれば、通信を暗号化する
smtp_tls_security_level = may

# サーバ証明書と秘密鍵を指定
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem

# TLSログレベルの設定
# 0:出力しない 1:TLSハンドシェイクと証明書情報 2:TLSネゴシエーションの全て
smtpd_tls_loglevel = 1

# 暗号に関する情報を "Received:" メッセージヘッダに含める
smtpd_tls_received_header = yes

# 接続キャッシュファイルの指定
smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache

# キャッシュの保持時間の指定
smtpd_tls_session_cache_timeout = 3600s

# 認証を通過したものはリレーを許可する(permit_sasl_authenticated)
smtpd_recipient_restrictions =
    permit_mynetworks
    permit_sasl_authenticated
    reject_unauth_destination
==========================================

smtps(SMTP-Auth over SSL)を有効化

[root@ip-10-0-200-67 ~]# vi /etc/postfix/master.cf

========== master.cfのファイル =============
#smtps     inet  n       -       n       -       -       smtpd
#  -o syslog_name=postfix/smtps
#  -o smtpd_tls_wrappermode=yes
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
 ↓
smtps     inet  n       -       n       -       -       smtpd
#  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
============================================

[root@ip-10-0-200-67 ~]# postfix check   //設定の間違いをチェック
[root@ip-10-0-200-67 ~]# systemctl start postfix 
[root@ip-10-0-200-67 ~]# systemctl enable postfix //自動起動の設定

Dovecotの設定

受信プロトコルの設定

[root@ip-10-0-200-67 ~]# vi /etc/dovecot/dovecot.conf

========== dovecot.confのファイル =============
#protocols = imap pop3 lmtp
 ↓
protocols = imap pop3
===============================================

ポートの設定

[root@ip-10-0-200-67 ~]# vi /etc/dovecot/conf.d/10-master.conf 

========== 10-master.confのファイル =============
service imap-login {
  inet_listener imap {
    #port = 143
     ↓
    port = 0
  }
  inet_listener imaps {
    #port = 993
    #ssl = yes
     ↓
    port = 993
    ssl = yes
  }
}

service pop3-login {
  inet_listener pop3 {
    #port = 110
     ↓
    port = 0
  }
  inet_listener pop3s {
    #port = 995
    #ssl = yes
     ↓
    port = 995
    ssl = yes
  }
}

〜 略 〜

service auth {
  # Postfix smtp-auth
  #unix_listener /var/spool/postfix/private/auth {
  #  mode = 0666
  #}
 ↓
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix        
  }
}
=================================================

認証方式の設定

[root@ip-10-0-200-67 ~]# vi /etc/dovecot/conf.d/10-auth.conf

========== 10-auth.confのファイル =============
#disable_plaintext_auth = yes
 ↓
disable_plaintext_auth = no

auth_mechanisms = plain
 ↓
auth_mechanisms = plain login
===============================================

SSL/TLSの有効化とサーバ証明書と秘密鍵を指定

[root@ip-10-0-200-67 ~]# vi /etc/dovecot/conf.d/10-ssl.conf

========== 10-ssl.confのファイル =============
ssl = required
 ↓
ssl = yes

#ssl_cert = </etc/pki/dovecot/certs/dovecot.pem
#ssl_key = </etc/pki/dovecot/private/dovecot.pem
 ↓
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem
==============================================

メールボックスの場所を設定

[root@ip-10-0-200-67 ~]# vi /etc/dovecot/conf.d/10-mail.conf

========== 10-mail.confのファイル =============
#mail_location = 
 ↓
mail_location = maildir:~/Maildir
==============================================
[root@ip-10-0-200-67 ~]# systemctl start dovecot   //dovecotの起動
[root@ip-10-0-200-67 ~]# systemctl enable dovecot   //自動起動の設定

ログの設定

【postfixのログの設定】

[root@ip-10-0-200-67 ~]# mkdir /var/log/mail
[root@ip-10-0-200-67 ~]# vi /etc/rsyslog.conf

========== rsyslog.confのファイル =============
mail.*                  -/var/log/maillog
         ↓
mail.*                  -/var/log/mail/maillog
==============================================

[root@ip-10-0-200-67 ~]# systemctl restart rsyslog   //syslogの再起動
[root@ip-10-0-200-67 ~]# rm -f /var/log/maillog*    //不要なログの削除
[root@ip-10-0-200-67 ~]# vi /etc/logrotate.d/syslog

========== syslogファイル =============
/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
   ↓ /var/log/maillogを削除
/var/log/cron
/var/log/messages
/var/log/secure
/var/log/spooler
=======================================

[root@ip-10-0-200-67 ~]# vi /etc/logrotate.d/maillog   //ログローテーション設定

========== maillogファイル =============
/var/log/mail/maillog {
    daily
    missingok
    dateext
    rotate 60
    sharedscripts
    postrotate
        /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}
=======================================

postfixログ設定の確認

[root@ip-10-0-200-67 ~]# logrotate -dv /etc/logrotate.d/maillog
Allocating hash table for state file, size 15360 B

Handling 1 logs

rotating pattern: /var/log/mail/maillog  after 1 days (60 rotations)
empty log files are rotated, old logs are removed
considering log /var/log/mail/maillog
  log does not need rotating (log has been already rotated)not running postrotate script, since no logs were rotated

【Dovecotのログの設定】

Dovecotログの出力先を変更

[root@ip-10-0-200-67 ~]# mkdir /var/log/dovecot
[root@ip-10-0-200-67 ~]# vi /etc/dovecot/conf.d/10-logging.conf

========== 10-logging.confのファイル =============
#log_path = syslog
 ↓
log_path = /var/log/dovecot/dovecot.log
==================================================

ログローテーション設定

[root@ip-10-0-200-67 ~]# vi /etc/logrotate.d/dovecot

========== dovecotファイル =============
/var/log/dovecot/dovecot.log {
    daily
    missingok
    dateext
    rotate 60
    sharedscripts
    postrotate
        /bin/kill -USR1 `cat /var/run/dovecot/master.pid 2>/dev/null` 2> /dev/null || true
    endscript
}
========================================

Dovecotログ設定の確認

[root@ip-10-0-200-67 ~]# logrotate -dv /etc/logrotate.d/dovecot
reading config file /etc/logrotate.d/dovecot
Allocating hash table for state file, size 15360 B

Handling 1 logs

rotating pattern: /var/log/dovecot/dovecot.log  after 1 days (60 rotations)
empty log files are rotated, old logs are removed
considering log /var/log/dovecot/dovecot.log
  log does not need rotating (log has been already rotated)not running postrotate script, since no logs were rotated

新規ユーザ作成時にMaildirを自動生成するようにします。

[root@ip-10-0-200-67 ~]# mkdir -p /etc/skel/Maildir/{new,cur,tmp}
[root@ip-10-0-200-67 ~]# chmod -R 700 /etc/skel/Maildir/

全ての設定が完了したら、postfixとDovecotを再起動します。
(私はここが抜けていて、1日ハマってしまいました。)

[root@ip-10-0-200-67 ~]# systemctl restart  postfix.service
[root@ip-10-0-200-67 ~]# systemctl restart dovecot.service

Thunderbirdの設定

新規ユーザの登録

[root@ip-10-0-200-67 ~]# useradd -s /sbin/nologin qiita_user
[root@ip-10-0-200-67 ~]# passwd qiita_user
Changing password for user qiita_user.
New password: (qiita)
Retype new password: (qiita)
passwd: all authentication tokens updated successfully.

これで、giita_user@example.comのメールアドレスが登録されます。

Thunderbirdの設定
mail2.png

[Thunderbirdはあなたのアカウト設定を見つけられませんでした。]というエラーが出た場合は、手動で設定します。

mail3.png

以上です。

うまくいかない場合は、下記をご確認ください。

1. postfixとDovecotのログ

postfixのログ
[root@ip-10-0-200-67 ~]# tail -f /var/log/mail/maillog
dovecotのログ
[root@ip-10-0-200-67 ~]# tail -f /var/log/dovecot/dovecot.log

2. ポートが開放されているかどうか

telnetコマンドを叩いてみる。
(コマンドが打てない場合は、インストールする必要があります。)

[root@ip-10-0-200-67 ~]# telnet mail.example.com 25
[root@ip-10-0-200-67 ~]# telnet mail.example.com 465
[root@ip-10-0-200-67 ~]# telnet mail.example.com 993
[root@ip-10-0-200-67 ~]# telnet mail.example.com 995

Portチェックテストで確認してみる。

また、メールサーバを構築する上で外部からの不正中継が可能な状態になっていないかの第三者中継チェックをしなければならないようです。
第三者中継チェック RBL.JP

以上です。

大変参考にさせていただきました。
ありがとうございました。
SSLメールサーバ構築メモ Let’s Encrypt+Postfix+Dovecot

POPとIMAPの違いと選択
yum の groupinstall でインストールされるパッケージを確認する
Let’s Encrypt で手軽に HTTPS サーバを設定する
telnetコマンドでのメールサーバ(SMTP, SMTP-AUTH, POP3)の動作確認
[Posftix]25番ポートに接続しようとして”Connection refused”の対処法

続きを読む

Fluentdの正規表現を攻略する

formatのデバッグツール

Fluentdのログの正規表現が正しいかはFluentularで確認しならが操作できる。
http://fluentular.herokuapp.com/

スクリーンショット 2017-03-27 3.18.16.png

ただし、これはHerokuの無料プランで動いているらしく使用できないことも多いのでDockerからでもできる

$ docker pull tomohiro/fluentular
$ docker run -p 8080:8080 tomohiro/fluentular

作成方法

定義されたフォーマットを指定するか正規表現を利用します。

定義されたフォーマット一覧

fluentd側で予め10個の定義が用意されています。デフォルトのログの設定を利用している場合はこれらを使用することができると思われるので使用すると良いでしょう。

  • apache2
  • apache_error
  • nginx
  • syslog
  • tsv
  • csv
  • ltsv
  • json
  • none
  • multiline

ただし、注意点としてこれは常に通用するものではなく、該当する場合にのみ使用することができます。

apache2

format /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

apache_error

format /^\[[^ ]* (?<time>[^\]]*)\] \[(?<level>[^\]]*)\](?: \[pid (?<pid>[^\]]*)\])? \[client (?<client>[^\]]*)\] (?<message>.*)$/

nginx

format /^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

syslog

format /^(?<time>[^ ]*\s*[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$/
time_format %b %d %H:%M:%S

tsv

format tsv
keys key1,key2,key3
time_key key2

csv

format csv
keys key1,key2,key3
time_key key3

ltsv

format ltsv
delimiter =               # Optional. ':' is used by default
time_key time_field_name

json

format json
time_key key3

none

format none
message_key my_message

multiline

format multiline
format_firstline /^Started/
format1 /Started (?<method>[^ ]+) "(?<path>[^"]+)" for (?<host>[^ ]+) at (?<time>[^ ]+ [^ ]+ [^ ]+)\n/
format2 /Processing by (?<controller>[^\u0023]+)\u0023(?<controller_method>[^ ]+) as (?<format>[^ ]+?)\n/
format3 /(  Parameters: (?<parameters>[^ ]+)\n)?/
format4 /  Rendered (?<template>[^ ]+) within (?<layout>.+) \([\d\.]+ms\)\n/
format5 /Completed (?<code>[^ ]+) [^ ]+ in (?<runtime>[\d\.]+)ms \(Views: (?<view_runtime>[\d\.]+)ms \| ActiveRecord: (?<ar_runtime>[\d\.]+)ms\)/

正規表現で記述する

名前付きキャプチャ

正規表現から該当箇所と通り出したいときに()で括ったところは$1,$2,$3,…のように取り出しますが、名前付きキャプチャを利用することで取り出しの部分に名前をつけることができます。

(?<name>pattern)

http://qiita.com/jnchito/items/cceb669cb06fc044f411

これだけは抑えておきたい正規表現

^ log $

^以降にログのパターンを書き始め、$で終端。

[pattern]

patternの文字列

[^pattern]

pattarn以外の文字列

\S

空白文字(半角スペース、\t、\n、\r、\f)以外すべて

.

任意の1文字

*

0文字以上

+

1文字以上

\special

エスケープ文字の役割を担う文字を文字そのものとして表現したいときは\を文字の前に付ける
[,],”, 空白

(?: pattern )

パターンのグループを表す。

( pattern1 | pattern2 )

pattern1またはpattern2

実際に正規表現を組み立てる

Apacheのhttpd.confにおけるログの設定は以下の通りとなります。

ErrorLog "logs/error_log"
LogLevel warn

<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %q" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b %q" common

    <IfModule logio_module>
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O %q" combinedio
    </IfModule>
    CustomLog "logs/access_log" combined
</IfModule>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

LogFormatの各項目の詳細は以下のURLを参照する。
https://httpd.apache.org/docs/2.4/ja/mod/mod_log_config.html

実際に吐き出されたアクセスログは以下の通りとなります。

10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"
10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1
10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"

3つめはELBによるヘルスチェックが記録されています。4つめはリクエストがタイムアウトされた様子を表しています。

10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"

上記のようなApacheのアクセスログを正規表現します。

正規表現 作成手順

1.最初に1つめから考えていきます。
最初は10.0.0.85から始まり、IPアドレスを表します。厳密に考えるとプライベートアドレスなどの範囲を考えてXXX.XXX.XXX.XXXの表記となるような表現となりますが、ログの吐き出しは吐き出しが限定されるので厳密にやる必要はなく、空白以外の文字が0文字以上となる表現でOK。空白以外は[^ ]で表されこれが0文字以上になるので[~ ]*で表される。これに名前付けをするために(?<host>[^ ]*)となる。最後にこのホスト名から始まるので^を先頭につけて、^(?<host>[^ ]*)となる。

2.次に空白を挟み、-が2回繰り返される。最初のremotelogが存在しないことによるもの。次の-はuserが存在しないことによるもの。(?<remotelog>[^ ]*) (?<user>[^ ]*)で表されます。空白に注意。以降空白の説明は省略します。

3.[03/Feb/2017:11:53:21 +0900]は時間を表します。時間については別途time_formatで指定します。[, ]ですが、これは特殊文字なので\でエスケープして使うのでこれは\[ time \]で表現します。次にtimeに相当するものですが、時間中には]は含まれないのでこれ以外の任意の文字列なので]は除外するためにこれ以外の文字が0文字以上となるので[^\]*を組み合わせて[^\]*で表現します。これに名前付けをするために(?<time>[^\]]*)となり、全て合わせて\[(?<time>[^\]]*)\]となります。
time_formatですが03/Feb/2017:11:53:21 +0900なのでそれぞれの日付表記に対応するフォーマットを使用して%d/%b/%Y:%H:%M:%S %zとなります。

4."GET /requests/form/34 HTTP/1.1"ですが、これは"で囲まれた要素に対して、GET/requests/form/34HTTP/1.1の要素から成ります。最初に"で囲まれているので"pattern"のようにします。
次にpatternについてですが、
GET /requests/form/34 HTTP/1.1ですが、GET(?<method>\S+)、このあとに空白が来て続いてくるので+/requests/form/34(?<path>[^ ]*)、空白が来るので、+HTTP/1.1が来るので\S*とします。

以下、同様にして組み立てていくと例えば、以下のような正規表現を作成することができます。

^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$

作成したものとFluentularの出力の見本は以下のURLとなります。
http://fluentular.herokuapp.com/parse?regexp=%5E%28%3F%3Chost%3E%5B%5E+%5D*%29+%28%3F%3Cremotelog%3E%5B%5E+%5D*%29+%28%3F%3Cuser%3E%5B%5E+%5D*%29+%5C%5B%28%3F%3Ctime%3E%5B%5E%5C%5D%5D*%29%5C%5D+%22%28%3F%3Cmethod%3E%5CS%2B%29%28%3F%3A+%2B%28%3F%3Cpath%3E%5B%5E+%5D*%29+%2B%5CS*%29%3F%22+%28%3F%3Cstatus%3E%5B%5E+%5D*%29+%28%3F%3Csize%3E%5B%5E+%5D*%29+%22%28%3F%3Creferer%3E%5B%5E%5C%22%5D*%29%22+%22%28%3F%3Cagent%3E.*%29%22%5C+*%28%3F%3Cquerystring%3E%5B%5E%5C%22%5D*%29%24&input=10.0.0.85+-+-+%5B03%2FFeb%2F2017%3A11%3A53%3A21+%2B0900%5D+%22GET+%2Frequests%2Fform%2F34+HTTP%2F1.1%22+200+95409+%22https%3A%2F%2Fsample.jp%2Fsample%3Fstation%3D%25E4%25B8%25AD%25E9%2587%258E%25E6%2596%25B0%25E6%25A9%258B%22+%22Mozilla%2F5.0+%28iPhone%3B+CPU+iPhone+OS+10_2_1+like+Mac+OS+X%29+AppleWebKit%2F602.4.6+%28KHTML%2C+like+Gecko%29+Mobile%2F14D27%22&time_format=%25d%2F%25b%2F%25Y%3A%25H%3A%25M%3A%25S+%25z

正しく作成されている場合、以下のように名前付きラベルに対応する値の内容が表示される。

スクリーンショット 2017-03-27 3.17.58.png

サンプル

アクセスログ

ログ

10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"


10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1

10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"

10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"

正規表現

^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$
%d/%b/%Y:%H:%M:%S %z

エラーログ

ログ

[Sun Feb 19 03:08:02.063361 2017] [auth_digest:notice] [pid 26649] AH01757: generating secret for digest authentication ...
[Sun Feb 19 03:08:02.065290 2017] [lbmethod_heartbeat:notice] [pid 26649] AH02282: No slotmem from mod_heartmonitor
[Sun Feb 19 03:08:02.203091 2017] [mpm_prefork:notice] [pid 26649] AH00163: Apache/2.4.23 (Amazon) PHP/5.6.22 configured -- resuming normal operations
[Sun Feb 19 03:08:02.203473 2017] [core:notice] [pid 26649] AH00094: Command line: '/usr/sbin/httpd'
[Mon Feb 20 23:34:38.774239 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Mon Feb 20 23:34:39.373401 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Wed Feb 22 10:12:12.180706 2017] [:error] [pid 17757] [client 10.0.1.172:10406] CSRF state token does not match one provided.
[Wed Feb 22 16:45:54.469282 2017] [:error] [pid 27395] [client 10.0.0.85:59532] CSRF state token does not match one provided.
[Wed Feb 22 16:48:53.326150 2017] [:error] [pid 27433] [client 10.0.0.85:59628] PHP Fatal error:  Call to a member function is_in_area() on null in /var/www/sample/releases/20170222074811/fuel/app/classes/controller/api/requests.php on line 116, referer: https://sample.jp/requests/confirm?judge=2
~

正規表現

^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$
%a %b %d %H:%M:%S.%N %Y

td-agent.confの設定例

td-agent.conf
<source>
  type tail
  format /^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$/
  time_format %d/%b/%Y:%H:%M:%S %z
  path /var/log/httpd/access_log
  tag sample.service.access.log.aggregate
  pos_file /tmp/fluent/logpos/access_log.pos
</source>

<source>
  type tail
  format /^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$/
  time_format %a %b %d %H:%M:%S.%N %Y
  path /var/log/httpd/error_log
  tag sample.service.error.log.aggregate
  pos_file /tmp/fluent/logpos/error_log.pos
</source>

<match **>
  type forward

  <server>
    name fluent01
    host 10.0.0.31
    port 24224
  </server>
</match>
td-agent.conf
<source>
  type forward
  port 24224
</source>

<match sample.service.access.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name access_log
    logstash_format true
    logstash_prefix sample-service-log-access
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path access-logs/
    buffer_path /tmp/fluent/s3/access/
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

<match sample.service.error.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name error_log
    logstash_format true
    logstash_prefix sample-service-log-error
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path error-logs/
    buffer_path /tmp/fluent/s3/error
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

(参照)権限設定

$ sudo chgrp td-agent /var/log/httpd/
$ sudo chgrp td-agent /var/log/messages

$ sudo chmod g+rx /var/log/httpd/
$ sudo chmod g+rx /var/log/messages

以上

Appendix

td-agent.conf パラメータ一覧

type
tag
path
exclude_path
refresh_interval
limit_recently_modified
skip_refresh_on_startup
read_from_head
read_lines_limit
multiline_flush_interval
pos_file
format
time_format
path_key
rotate_wait
enable_watch_timer

参考

http://docs.fluentd.org/v0.12/articles/in_tail
http://www.tachibanakikaku.com/2013/12/fluentdformat.html

続きを読む