[Ansible]CloudWatch Logs で Play Framework のログを見てみる
はじめに
CloudWatch Logs で Play Framework のログを出力してみるまでの手順を ansible playbook と rake タスクとしてまとめてみました。今回はその事前設定と、タスクと ansible role の説明、及び play framework の logback の設定方法をざっくりと解説していきます。
今回のサンプルコードは以下に上がっています。
- ansible playbook の role群とそれをラップした rake タスクをまとめたデプロイコードのリポジトリ
- logback に必要な変更を施した play framework アプリコードのリポジトリ
事前設定
今回のサンプルコードはEC2上にデプロイされることを想定したものですが、そのEC2に対してはCloudWatch Logsに対するアクセス権限が必要となります。CloudWatch Logs へのアクセス権限を持った IAM Role を作成し、EC2インスタンス作成時に付与してください。そのための資料としては以下の記事をご覧いただければとおもいます。
クイックスタート: 既存の EC2 インスタンスに CloudWatch Logs エージェントをインストールして設定する
デプロイコード
ansible playbook の role群とそれをラップした rake タスクのリポジトリは以下の様なディレクトリ構成になっています。
. ├── Gemfile ├── Rakefile ├── ansible │ ├── inventory │ │ └── hosts │ ├── roles │ │ ├── awslogs │ │ ├── common │ │ ├── iptables │ │ ├── java │ │ ├── play │ │ └── yum │ └── site.yml └── tasks ├── play.rake └── provision.rake
事前に
$ bundle install --path vendor/bundle
した上で ansible を適宜インストールしてください。
rake タスク
アプリのデプロイに必要な rake のタスクは以下のようになっています。
$ bundle exec rake -vT rake play:build # build for release rake play:checkout # checkout from repository rake play:prepare # prepare for build rake provision # provision servers
rake play:build
このタスクはplay:checkoutとplay:prepareの双方をふくんでいます。やっていることは単純で先ほどの logback に必要な変更を施した play framework アプリを clone してきて本番用のビルドを行うというものになっています。
ビルド成果物のはのちの provision タスクで EC2 にアップロードされます。ビルドをEC2上で行うよりもローカルマシンで行うほうが早い場合にこの方法は有効です。
rake provision
このタスクは ansible playbook をラップしたものです。SSH_KEY_PATH に EC2 の sshキーを指定することで ansible 内で指定された host に対してプロビジョニングを実行します。
ansible
ansible/roles 配下ではプロビジョニングに必要な各roleが切り出されています。以下のsite.ymlで定義された各roleが実行されます。
site.yml
--- - hosts: '{{ hosts }}' sudo: yes roles: - { role: yum } - { role: common } - { role: iptables } - { role: java } - { role: awslogs } - { role: play, app_name: 'cloudwatchlogsample' }
各Roleの簡単な概要です。
- yum : yum パッケージを最新にし、epelレポジトリをインストールします。
- common : タイムゾーンに関する設定を行います。
- iptables : HTTP80ポートのパケットをplay frameworkのデフォルトポート9000に転送する設定を行います。
- java : java8をインストールします
- awslogs : CloudWatch Logs にログ転送を行うエージェントをインストールし、必要な設定を行います。後で詳しく見ていきます。
- play : 先ほどのビルド成果物を転送し、init.d配下にサービスとして登録します。サービス登録後に起動を行います。
このプロビジョニングによって CloudWatch Logs にログを流し込むデーモンが EC2 上に立ち上がり、サービスとして登録された play framework の実行スクリプトが走り、ワンライナーでアプリとログ取得に必要な環境設定が全て構築されることになります。
awslogs role
awslogs のロールについて見ていきます。
awslogs/tasks/main.yml
- name: install awslogs yum: name=awslogs state=latest disable_gpg_check=yes - name: copy conf files template: src={{ item }}.j2 owner=root dest=/etc/awslogs/{{ item }} with_items: - awscli.conf - awslogs.conf - name: create directory file: path=/home/ec2-user/awslogs/state owner=ec2-user state=directory mode=0775 - name: create state log file file: path=/home/ec2-user/awslogs/state/agent-state owner=ec2-user state=touch mode=0775 - name: start agent service: name=awslogs state=started
- install awslogs : yum で CloudWatch Logs のエージェント awslogs パッケージをインストールします。
- copy conf files : awslogs の起動に必要な設定ファイルを必要な箇所に上書きします。srcに示されているawscli.conf.j2, awslogs.conf.j2ファイルは以下のとおりです。
awslogs/templates/awscli.conf.j2
[plugins] cwlogs = cwlogs [default] region = ap-northeast-1
regionではCloudWatchLogsのリージョンを指定します。
awslogs/templates/awslogs.conf.j2
# # ... 省略 # [general] # Path to the CloudWatch Logs agent's state file. The agent uses this file to maintain # client side state across its executions. state_file = /home/ec2-user/awslogs/state/agent-state # # ... 省略 # [home/ec2-user/logs/] log_group_name = playsample log_stream_name = stream-sample-log file = /home/ec2-user/logs/application.log datetime_format = %Y-%m-%d %H:%M:%S
log_group_nameでロググループ名、log_stream_nameでログストリーム名、fileで監視対象ログファイルを記述し、datetime_format でログのフォーマットを指定します。
- create directory, create state log file : awslogs エージェントのstateを記録するためのログファイルを作成します。
- start agent : awslogs サービスを開始します。
アプリコード
logback.xmlのカスタマイズを先ほどのawslogsの設定ファイルと適合する形で行います。
conf/logback.xml
<configuration> <conversionRule conversionWord="coloredLevel" converterClass="play.api.Logger$ColoredLevel" /> <appender name="FILE_ERROR" class="ch.qos.logback.core.FileAppender"> <file>/home/ec2-user/logs/error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>/home/ec2-user/logs/error%i.log</fileNamePattern> <minIndex>1</minIndex> <maxIndex>20</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>50000</maxFileSize> </triggeringPolicy> <encoder> <pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%level] from %logger in %thread - %replace(%message){`\n`, ` `} %ex %n</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <appender name="FILE_INFO" class="ch.qos.logback.core.FileAppender"> <file>/home/ec2-user/logs/application.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>/home/ec2-user/logs/application%i.log</fileNamePattern> <minIndex>1</minIndex> <maxIndex>20</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>50000</maxFileSize> </triggeringPolicy> <encoder> <pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%level] from %logger in %thread - %replace(%message){`\n`, ` `} %n</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%coloredLevel - %logger - %message %exception</pattern> </encoder> </appender> <!-- The logger name is typically the Java/Scala package name. This configures the log level to log at for a package and its children packages. --> <logger name="play" level="INFO" /> <logger name="application" level="DEBUG" /> <root level="WARN"> <appender-ref ref="FILE_ERROR" /> <appender-ref ref="FILE_INFO" /> <appender-ref ref="STDOUT" /> </root> </configuration>
logback.xml作成時にはlogbackでレベルごとに出力するファイルを分けるを参考にしました。
ログレベルに応じて追記するログファイルを振り分け、rollingPolicyとtriggeringPolicyでログローテーションの設定をしています。ログローテーションを実行している場合でも CloudWatch Logs は以下の条件であればサポートされます。
- ファイルがリネームされ、同名のファイルが新規作成される
- ファイルがコピーされ、中身がトランケートされる
- ファイルが同じような条件で新規作成される
デプロイと確認
必要な前設定をした上でビルドスクリプトを実行
$ bundle exec rake play:build
IAM Roleの設定されたEC2を立ち上げた上でIPを ansible/inventory/hosts の下に追記して
ansible/inventory/hosts
[web] xxx.xxx.xxx.xxx
sshキーを指定してデプロイスクリプトを実行します。
$ bundle exec rake provision SSH_KEY_PATH=YOUR_SSH_KEY_PATH
デプロイに成功したらコンソールをひらき、ログが追記されていることを確認します。
作成時刻等のカラムの表示設定は右上の歯車ボタンで行えます。
デプロイとログ取得をワンライナーで行えると気持ちいいですね!