AWSでログ監視をなるべくansibleから設定する

# AWSでログ監視をなるべくansibleから設定する

## ゴール

アプリケーションのログをCloudWatchで監視し、アラームがあげられるようなec2サーバ環境のansibleによる自動構築

## 注意

いくつかわかったことと、わからないことがあり、わからないことが解決したら書き換えていきます。
必ずしも全てあっているわけではありません。

## ansibleによるec2インスタンスの構築・立ち上げ

[ansibleとは](https://ja.wikipedia.org/wiki/Ansible_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)) サーバ等を自動構築するための構成管理ツール

Ansilbe [All Modules](https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html#all-modules)を見ると、多くのaws設定モジュールがansibleには存在します。
コンソール、aws cliでできることが完全に網羅されていませんがこれらのモジュールを使うとansibleの中で構成変更とその結果を出力することができます。

aws cliで構築するやり方を[AWS CLIで構築するCloudWatch Agent](https://dev.classmethod.jp/cloud/aws/cloudwatch-agent-aws-cli/)より参考としました。

### ec2インスタンスの構築公開は後回しにします。

## ansibleによるec2サーバの設定

ここではawsに構築されたec2インスタンスにパッケージ等インストールしてCloudWatchでサーバの監視ができる状態をansibleで作ります。

ec2は構築のたびにIPとか変わります。そのためansibleの静的設定ファイルにホストの指定ができません。そのためawsからそれを取得するEC2 External Inventoryというツールがあります。[ここ](https://www.kabegiwablog.com/entry/2018/01/25/090000)などを参考にこれを導入しました。


### まずは必要なパッケージのインストール

次のようなansibleの構成にて単純なパッケージの導入と起動とかを設定していきます。

```
.
├── hosts
│   ├── prod
│   └── stg
│       ├── ec2.ini
│       └── ec2.py
├── roles
│   └── packages
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── site.yml
```

#### 導入したいパッケージ

次の2つが必要なパッケージだがAmazon Linux 2のAMIを選択してインスタンスを作っておいたらすでにインストール済みでした。

```
amazon-cloudwatch-agent
amazon-ssm-agent
```

インスタンスにログインし次を実行。

```console
[ec2-user@ip-10-1-21-4 bin]$ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -m ec2 -a status
{
  "status": "stopped",
  "starttime": "",
  "version": "1.208036.0"
}
[ec2-user@ip-10-1-21-4 bin]$
```

---

加えてこちらのパッケージを導入。

```
collectd
```
[collectdの設定方法](https://qiita.com/28tomono/items/33d3520e81f39fe2f96f)を参考とさせていただきました。

collectdで集めた情報をCloudWatchから参照することができる。

```
aws cloudwatch ...
```

というcliもあるがcollectd等と連携することが推奨されているようだ。

#### 次のようにcollectdの設定を変更して再起動する。

```yaml
- name: collectd configuration
  lineinfile: >-
    dest='/etc/collectd.conf'
    state=present
    backrefs=yes
    regexp='{{ item.regexp }}'
    line='{{ item.line}}'
  with_items:
  - regexp: '^#?\s*LoadPlugin disk'
    line: 'LoadPlugin disk'
  - regexp: '^#?\s*LoadPlugin email'
    line: 'LoadPlugin email'
  register: conf
- debug: var=conf

- name: dependency service restart
  service:
    name: collectd
    state: restarted
```

------

## CloudWatchに情報を送るIAMユーザを作成

次のポリシーをアタッチしたIAMユーザを作成

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "cloudwatch:PutMetricData",
            "Resource": "*"
        }
    ]
}
```
[AWS Sytems Manager Sericesからコマンド実行のトラブルシューティング(原因不明)](https://qiita.com/kaorunix/items/f4cb3d98c15c8cf9456c)が発生。

[CloudWatch エージェント設定ファイルを手動で作成または編集する](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html)を見て頑張って設定ファイルを作ってみる。

一度「Amazon Web Services パターン別構築・運用ガイド」を読んで実行していたのでP.414で生成されたファイルを元に変更している。

### metrics

diskの計測方法(measurement)を変えている

```json
    "metrics": {
	"append_dimensions": {
	    "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
	    "ImageId": "${aws:ImageId}",
	    "InstanceId": "${aws:InstanceId}",
	    "InstanceType": "${aws:InstanceType}"
	},
	"metrics_collected": {
	    "collectd": {
		"metrics_aggregation_interval": 60
	    },
	    "cpu": {
		"measurement": [
		    "cpu_usage_idle",
		    "cpu_usage_iowait",
		    "cpu_usage_user",
		    "cpu_usage_system"
		],
		"metrics_collection_interval": 60,
		"resources": [
		    "*"
		],
		"totalcpu": false
	    },
	    "disk": {
		"measurement": [
		    "total",
		    "used",
		    "free",
		    "used_percent",
		    "inodes_free"
		],
		"metrics_collection_interval": 60,
		"resources": [
		    "*"
		]
	    },
	    "diskio": {
		"measurement": [
		    "io_time",
		    "write_bytes",
		    "read_bytes",
		    "writes",
		    "reads"
		],
		"metrics_collection_interval": 60,
		"resources": [
		    "*"
		]
	    },
	    "mem": {
		"measurement": [
		    "mem_used_percent"
		],
		"metrics_collection_interval": 60
	    },
	    "netstat": {
		"measurement": [
		    "tcp_established",
		    "tcp_time_wait"
		],
		"metrics_collection_interval": 60
	    },
	    "statsd": {
		"metrics_aggregation_interval": 60,
		"metrics_collection_interval": 10,
		"service_address": ":8125"
	    },
	    "swap": {
		"measurement": [
		    "swap_used_percent"
		],
		"metrics_collection_interval": 60
	    }
	}

```

### logs

ログはデフォルトでmessagesの監視がついていたがそれをコピーしてアプリケーションのログを監視できるようにした。

```json
    "logs": {
	"logs_collected": {
	    "files": {
		"collect_list": [
		    {
			"file_path": "/var/log/messages",
			"log_group_name": "messages",
			"log_stream_name": "{instance_id}"
		    },
		    {
			"file_path": "/var/log/application.log",
			"log_group_name": "application",
			"log_stream_name": "{instance_id}"
		    }
		]
	    }
	}
    },
```

### agent

ウィザードを使って生成した元にはagentがなかったが、ログを見てみようと思いdebugを入れた。

```json
    "agent": {
	"debug": true
    },
```



```console
 $ aws ssm describe-instance-information --instance-information-filter-list key=PingStatus,valueSet=Online                                                                                                                    [17:48:26]
{
    "InstanceInformationList": [
        {
            "IsLatestVersion": false, 
            "ComputerName": "ip-x-x-x-x.us-west-2.compute.internal", 
            "PingStatus": "Online", 
            "InstanceId": "i-xxxx7cb53516b635e", 
            "IPAddress": "xx.xx.xx.xx", 
            "ResourceType": "EC2Instance", 
            "AgentVersion": "2.3.372.0", 
            "PlatformVersion": "2", 
            "PlatformName": "Amazon Linux", 
            "PlatformType": "Linux", 
            "LastPingDateTime": 1554108523.909
        }
    ]
}
```
## 設定ファイルを配布する

[AWS CLIで構築するCloudWatch Agent](https://dev.classmethod.jp/cloud/aws/cloudwatch-agent-aws-cli/)
の **CloudWatch Agentの設定ファイルをパラメータストアにアップロード** を参考に
AWS SYSTEM MANAGERのパラメータストア にアップロードすることにした。

ansibleにもaws_ssm_parameter_storeというパラメータストアの管理モジュールがありましたので、それを使います。
値は設定ファイルから設定しています。

* aws.cloudwatchagent.name AmazonCloudWatch-linux
* aws.cloudwatchagent.file 上記設定ファイルを別ファイルとしてパスを記載


```yaml
- name: copy conf file SSM parameter store
  aws_ssm_parameter_store:
    region: "{{ aws.common.region }}"
    name: "{{ aws.cloudwatchagent.name }}"
    description: "{{ aws.cloudwatchagent.description }}"
    string_type: String
    value: "{{ lookup('file',aws.cloudwatchagent.file) }}"
  register: aws_ssm_parameter_store
- debug: var=aws_ssm_parameter_store

```

### 設定を反映する。

[AWS CLIで構築するCloudWatch Agent](https://dev.classmethod.jp/cloud/aws/cloudwatch-agent-aws-cli/)
の **CloudWatch Agent設定** を参考にansibleを定義します。
モジュールは存在しなかったのでaws cliを実行することにしました。

ec2インスタンスはすでに構築されている前提で、まずはec2のインスタンスIDを取得します。
私の環境ではsystemというタグでいくつかグループを分けていたためフィルタに設定しています。各自の環境に読み替えてください。

```yaml
- name: get ec2 instanceids
  ec2_instance_facts:
    region: "{{ aws.common.region }}"
    filters:
      "tag:system": "{{ aws.common.target }}"
  register: ec2_instance_facts
- debug: var=ec2_instance_facts
```

次に取得したインスタンスIDに対してコマンドを実行します。

```yaml
- name: aws CloudWatch config
  command: >
    aws ssm send-command 
      --document-name "AmazonCloudWatch-ManageAgent" 
      --targets "Key=instanceids,Values='{{ item.instance_id }}'" 
      --parameters "action=configure, mode=ec2, optionalConfigurationSource=ssm, optionalConfigurationLocation=AmazonCloudWatch-linux, optionalRestart=yes" 
      --profile {{ aws.common.profile }}
      --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --region "{{ aws.common.region }}"
  with_items: "{{ ec2_instance_facts.instances }}"
```

うまく実行できたら AWS コンソールからログインし、CloudWatchのメトリクスを確認します。
CWAgentにCloudWatchAgentの設定を行なったec2インスタンスが追加されていれば成功です。

```
.
├── hosts
│   ├── prod
│   └── stg
│       ├── ec2.ini
│       └── ec2.py
├── roles
│   ├── cloudwatch
│   │   ├── tasks
│   │   │   ├── main.yml
│   │   └── vars
│   │       ├── main.yml
│   ├── iam_role
│   │   └── tasks
│   │       └── main.yml
│   └── packages
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── site.yml
```