ようへいの日々精進XP

よかろうもん

ラムダこりゃ外伝 - Lambda ファンクションから IAM role の AWS クレデンシャル情報を取得する for Python

tl;dr

  • Lambda ファンクションから IAM role の AWS クレデンシャル情報を取得したい

どうするのか

参考

qiita.com

ありがとうございます!!mm

環境変数から取得する

以下のようなサンプルファンクション。

import os

def lambda_handler(event, context):
    
    print "----------------------------------------"
    print os.environ.get('AWS_ACCESS_KEY_ID')
    print os.environ.get('AWS_SECRET_ACCESS_KEY')
    print os.environ.get('AWS_SESSION_TOKEN')
    print "----------------------------------------"

IAM role には他の Lambda ファンクション作成時に自動的に作成された IAM role の lambda_basic_execution を付与。

実行してみると...

START RequestId: aae46b65-97e5-11e6-8038-c5c1480ca076 Version: $LATEST
----------------------------------------
Axxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
----------------------------------------
END RequestId: aae46b65-97e5-11e6-8038-c5c1480ca076
REPORT RequestId: aae46b65-97e5-11e6-8038-c5c1480ca076 Duration: 0.22 ms   Billed Duration: 100 ms    Memory Size: 128 MB    Max Memory Used: 7 MB

いい感じ。

Amazon Elasticsearch に接続する例

AWS4Auth との合わせ技。

  • IAM role

サービスロールには AWS Lambda を選択し、以下のようなポリシーをインラインポリシーにて付与。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "es:*"
            ],
            "Resource": "*"
        }
    ]
}
  • ファンクション
from elasticsearch import Elasticsearch, RequestsHttpConnection
from requests_aws4auth import AWS4Auth
import requests
import json, os

def lambda_handler(event, context):
    awsauth     = AWS4Auth(
        os.environ.get('AWS_ACCESS_KEY_ID'),
        os.environ.get('AWS_SECRET_ACCESS_KEY'),
        'ap-northeast-1',
        'es',
        session_token=os.environ.get('AWS_SESSION_TOKEN')
    )

    es = Elasticsearch(
        hosts=[{'host': 'search-oreno-es-xxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com', 'port': 443}],
        http_auth=awsauth,
        use_ssl=True,
        verify_certs=True,
        connection_class=RequestsHttpConnection
    )

    return json.dumps(es.info())

実行してみると以下のような感じ。

"{\"cluster_name\": \"xxxxxxxxxxxxxx:oreno-es\", \"tagline\": \"You Know, for Search\", \"version\": {\"lucene_version\": \"5.5.0\", \"build_hash\": \"0944b4bae2d0f7a126e92b6133caf1651ae316cc\", \"number\": \"2.3.2\", \"build_timestamp\": \"2016-05-20T07:46:04Z\", \"build_snapshot\": false}, \"name\": \"Wraith\"}"

おけ。

以上

メモでした。

pip install --target オプションがエラーになって泣きそうになったメモ

tl;dr

pip install -r requirements.txt --target . ってしたらエラーになって詰んでしまったのでメモ。

どうしたのか

エラー

ウオ...

Exception:
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/pip/basecommand.py", line 209, in main
    status = self.run(options, args)
  File "/usr/lib/python2.7/dist-packages/pip/commands/install.py", line 335, in run
    prefix=options.prefix_path,
  File "/usr/lib/python2.7/dist-packages/pip/req/req_set.py", line 732, in install
    **kwargs
  File "/usr/lib/python2.7/dist-packages/pip/req/req_install.py", line 837, in install
    self.move_wheel_files(self.source_dir, root=root, prefix=prefix)
  File "/usr/lib/python2.7/dist-packages/pip/req/req_install.py", line 1039, in move_wheel_files
    isolated=self.isolated,
  File "/usr/lib/python2.7/dist-packages/pip/wheel.py", line 247, in move_wheel_files
    prefix=prefix,
  File "/usr/lib/python2.7/dist-packages/pip/locations.py", line 153, in distutils_scheme
    i.finalize_options()
  File "/usr/lib/python2.7/distutils/command/install.py", line 289, in finalize_options
    raise DistutilsOptionError("can't combine user with prefix, "
DistutilsOptionError: can't combine user with prefix, exec_prefix/home, or install_(plat)base

エラーメッセージで検索すると以下のような対処方法がヒットするが、何れも --targe オプション利用時の事例や対処方法ではなく...「これは困った」状態。

stackoverflow.com

--user オプションと --prefix オプションなんて使って無いけどなあと思いつつ。

対処

結局、以下のように対応。

stackoverflow.com

pip install --user --install-option="--install-purelib=/path/to/target_dir" -r requirements.txt

ドキュメント を確認すると --install-purelib は pure Python モジュールのインストール先を指定するオプションのようだ。

うーん

根本解決してない :desuyone:

Amazon Elasticsearch Service の Kibana にプロキシ経由でアクセスする考察

tl;dr

Amazon ES の Kibana へのアクセスってログ取れたらいいのになーと思ったので、以下の記事を参考に Kibana の前に Nginx か何かでプロキシサーバーを立ててみることにした。

やったこと

構成イメージ

f:id:inokara:20161022120837p:plain

プロキシ用 EC2 の作成

  • CentOS 6.x
  • IAM role を付与しておく(ポリシーは以下のポリシーを付与)
- AmazonEC2ReadOnlyAccess
- CloudWatch-Logs

CloudWatch-Logs は以下のようなポリシーとなっている。(Nginx のログを CloudWatch Logs に送る為)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*"
            ]
        }
    ]
}

Amazon ES の作成

  • AccessPolicy の設定で EC2 の IP を設定しておく(例:xx.xxx.xx.0)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:xxxxxxxxxxxx:domain/oreno-es/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "xx.xxx.xx.0"
        }
      }
    }
  ]
}

EC2 への Nginx インストールと設定

$ cd ~/
$ openssl genrsa 2048 > server.key
$ openssl req -new -key server.key > server.csr
$ openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt
$ sudo mv server.* /etc/nginx/conf.d/
$ cd /etc/nginx/conf.d/
$ sudo chown root:root server.*
  • Nginx の設定
$ cat /etc/nginx/conf.d/es.conf
server {

  listen       443  default ssl;
  ssl on;
  ssl_certificate     /etc/nginx/conf.d/server.crt;
  ssl_certificate_key /etc/nginx/conf.d/server.key;

  server_name oreno-es.inokara.com;
  proxy_set_header Host $http_host;
  location / {
      proxy_pass https://search-oreno-es-xxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com/;
  }
}
  • Nginx の再起動
sudo service nginx restart

CloudWatch Logs Agent のセットアップ

  • /var/awslogs/etc/awslogs.conf
$ sudo cat /var/awslogs/etc/awslogs.conf

(snip)

[/var/log/nginx/access.log]
datetime_format = %d/%b/%Y:%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 = /var/log/nginx/access.lo

Kibana へのアクセス

f:id:inokara:20161022114202p:plain

やったー、アクセス出来た。

アクセスログ

  • プロキシ用サーバー上で確認
$ sudo tail -f /var/log/nginx/access.log

(snip)
xxx.xxx.xx.xxx - - [22/Oct/2016:11:28:46 +0900] "POST /.kibana-4/_mget?timeout=0&preference=1477103333038 HTTP/1.1" 200 2843 "https://xxx.xxx.xx.0/_plugin/kibana/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36" "-"
xxx.xxx.xx.xxx - - [22/Oct/2016:11:28:46 +0900] "GET /_plugin/kibana/bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.2.0 HTTP/1.1" 200 65452 "https://xxx.xxx.xx.0/_plugin/kibana/styles/main.css?_b=7562" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36" "-"
xxx.xxx.xx.xxx - - [22/Oct/2016:11:28:47 +0900] "GET /_plugin/kibana/styles/theme/elk.ico HTTP/1.1" 200 1150 "https://xxx.xxx.xx.0/_plugin/kibana/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36" "-"
xxx.xxx.xx.xxx - - [22/Oct/2016:11:28:47 +0900] "POST /.kibana-4/__kibanaQueryValidator/_validate/query?explain=true&ignore_unavailable=true HTTP/1.1" 200 190 "https://xxx.xxx.xx.0/_plugin/kibana/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36" "-"
xxx.xxx.xx.xxx - - [22/Oct/2016:11:28:47 +0900] "POST /cwl-2016.10.22/_msearch?timeout=0&preference=1477103333038 HTTP/1.1" 200 9323 "https://xxx.xxx.xx.0/_plugin/kibana/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36" "-"
  • CloudWatch Logs でも確認

f:id:inokara:20161022114947p:plain

以上

感想

  • 思ったよりも簡単にプロキシ経由で Kibana にアクセスすることが出来た

メリ

  • Kibana へのアクセス(厳密には Amazon ES へのアクセスも)を EC2 のセキュリティグループや Web サーバーの認証機構を利用出来る
  • Amazon ES のアクセスポリシー変更作業に依存しなくなる(構成変更に時間が掛かる...)、EC2 の IP だけアクセスポリシーに設定しておけば良い

出目

  • EC2 を用意する必要がある
  • 同時に EC2 の管理を行う必要がある

解決出来ていない課題

  • Kibana だけアクセスさせるという課題はプロキシでも解決出来ない(Kibana にアクセスさせるけど Elasticsearch API を叩かせないということは出来なさそう)

Amazon Aurora のスロークエリログを logstash を使って Amazon ES に放り込む

tl;dr

  • Amazon Aurora のスロークエリログを Logstash を使って Amazon ES に放り込みたい
  • サーバーレスでやりたい場合には Lambda を利用する方法が既に紹介されている
  • 今回はあえて logstash を使う

メモ

参考

ありがとうございますmm

Aurora のスロークエリログについて

ファイルに吐く場合に以下のような挙動となるようだ。

  • slow_query_log を ON にすると一時間に一回 mysql-slowquery.log.YYYY-MM-DD.HH というログが生成される
  • general_log を ON にすると mysql-slowquery.log というログが生成されてリアルタイムに確認出来る

今回は一時間に一回 cron でスロークエリログを API で取得して logstash 経由で Amazon ES に放り込むことにする。

試した構成

f:id:inokara:20161022145115p:plain

試す環境

$ cat /etc/redhat-release 
CentOS release 6.8 (Final)

$ python -V
Python 2.6.6

$ /opt/logstash/bin/logstash --version
logstash 2.4.0

$ sudo /opt/logstash/bin/plugin list --installed --verbose logstash-output-amazon_es
logstash-output-amazon_es (1.0)

logstash のインストール

$ sudo yum install -y java-1.8.0-openjdk.x86_64
$ sudo rpm --import https://packages.elastic.co/GPG-KEY-elasticsearch
$ sudo sh -c 'cat << EOT >> /etc/yum.repos.d/logstash.repo
[logstash-2.4]
name=Logstash repository for 2.4.x packages
baseurl=https://packages.elastic.co/logstash/2.4/centos
gpgcheck=1
gpgkey=https://packages.elastic.co/GPG-KEY-elasticsearch
enabled=1
EOT'
$ sudo yum install -y logstash

logstash の Amazon ES プラグインの導入

$ cd /opt/logstash
$ sudo bin/plugin install logstash-output-amazon_es

以下のように出力される。

The use of bin/plugin is deprecated and will be removed in a feature release. Please use bin/logstash-plugin.
OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
Validating logstash-output-amazon_es
Installing logstash-output-amazon_es
Installation successful

SDK を利用してスロークエリログを取得する

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import boto3
import sys
import re
from pytz import timezone
from datetime import datetime

# DB 名を指定
DBINSTANCEIDENTIFIER = 'oreno-aurora'

# 不要な行
LINES = [
    "/rdsdbbin/oscar/bin/mysqld, Version: 5.6.10-log (MySQL Community Server (GPL)). started with:",
    "Tcp port: 3306  Unix socket: /tmp/mysql.sock",
    "Time                 Id Command    Argument"
]

# 取得するログファイルの日付を生成
LOGDATETIME = datetime.now(timezone('UTC')).strftime("%Y-%m-%d.%H")

# RDS への接続
rds = boto3.client('rds', region_name='ap-northeast-1')

# ログの取得
try:
    body = rds.download_db_log_file_portion(
            DBInstanceIdentifier = DBINSTANCEIDENTIFIER,
            LogFileName = 'slowquery/mysql-slowquery.log.' + LOGDATETIME,
    )['LogFileData']
except Exception as e:
    print {'result': 'error', 'description': e}
    sys.exit (1)

# 取得したログを一行ずつ展開(LINES に含まれる行と ^# Time: にマッチする行を削除)
for line in body.split("\n"):
    if not line or line in LINES or re.match("^# Time:" , line):
        continue
    else:
        print line

EC2 に DownloadDBLogFilePortion ポリシーをアタッチする

既に適用済みの IAM role に DownloadDBLogFilePortion ポリシーをアタッチする。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "RDSSlowLogDownloadPolicy",
            "Action": [
                "rds:DownloadDBLogFilePortion"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

試しにログを出力させてみる。

$ python rds-slow-log.py
# User@Host: xxxxxxx[xxxxxxx] @  [xx.x.x.xxx]  Id:     6
# Query_time: 7.000184  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
SET timestamp=1477110716;
SELECT SLEEP(7);

(snip)

# User@Host: xxxxxxx[xxxxxxx] @  [xx.x.x.xxx]  Id:    48
# Query_time: 8.000187  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
SET timestamp=1477112288;
SELECT SLEEP(8);
# User@Host: xxxxxxx[xxxxxxx] @  [xx.x.x.xxx]  Id:    49
# Query_time: 11.000176  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
SET timestamp=1477112330;
SELECT SLEEP(11);
# User@Host: xxxxxxx[xxxxxxx] @  [xx.x.x.xxx]  Id:    50
# Query_time: 6.000193  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
SET timestamp=1477112366;
SELECT SLEEP(6);

logstash の設定

# input
input {
  stdin {
    codec => multiline {
      pattern => "^# User@Host:"
      negate => true
      what => previous
    }
  }
}

# filter
filter {
  grok {
    match => [ "message", "^# User@Host: %{USER:user}(?:\[[^\]]+\])?\s+@\s+\[%{IP:client}?\]\s+Id:\s+%{NUMBER:client_id:int}\n# Query_time: %{NUMBER:duration:float}\s*Lock_time: %{NUMBER:lock_wait:float}\s*Rows_sent: %{NUMBER:rows_sent:int}\s*Rows_examined: %{NUMBER:rows_examined:int}\nSET timestamp=%{NUMBER:timestamp};\n%{GREEDYDATA:sql_query};"]
  }

  date {
    match => [ "timestamp", "UNIX" ]
  }

}

# output
output {
  # for Debug
  # stdout { codec => json }
  amazon_es {
    hosts => ["search-xxxxxxxxx.ap-northeast-1.es.amazonaws.com"]
    region => "ap-northeast-1"
    index => "rds_slow_log-%{+YYYY.MM.dd}"
  }
  file {
    path => "/path/to/rds_slow_log-%{+YYYY-MM-dd-HH}.json"
  }
}

cron で実行

以下のようなラッパースクリプトを作成。

#!/usr/bin/env bash

_LOG_DATE_TIME=$(date +%Y-%m-%d_%H)
_DELETE_LOG_DATE=$(date --date '7 day ago' +%Y-%m-%d)

_ROOT="/path/to/rds-slow-log"
_LOGSTASH_CONF="rds-slow-log.conf"
_LOGSTASH_LOG="rds-slow-log.log"

ls ${_ROOT}/${_LOGSTASH_LOG}.${_DELETE_LOG_DATE}_* >/dev/null 2>&1
[ $? -eq 0 ] && rm -f ${_ROOT}/${_LOGSTASH_LOG}.${_DELETE_LOG_DATE}_*

${_ROOT}/bin/rds-slow-log.py | \
  /opt/logstash/bin/logstash \
    -f ${_ROOT}/conf/${_LOGSTASH_CONF} \
    -l ${_ROOT}/log/${_LOGSTASH_LOG}.${_LOG_DATE_TIME}

cron は以下のように設定。

3 * * * *    /path/to/rds-slow-log/bin/run.sh >/dev/null 2>&1

EC2 に Amazon ES へデータをポストする為のポリシーをアタッチする

既に適用済みの IAM role に ESHttpPost ポリシーをアタッチする。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "es:ESHttpPost",
            "Resource": "arn:aws:es:*:*:*"
        }
    ]
}

ダミーのクエリを仕込む

以下のようなスクリプトであえてスロークエリが発生するようにする。

while true;
do
  sleep 30
  TIME=`awk 'BEGIN{srand();print int(rand() * 15)}'`
  MYSQL_PWD="xxxxxxxxxxxx" mysql -udbadmin -h oreno-aurora-1.cluster-xxxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com --execute "SELECT SLEEP(${TIME});"
done

しばらくすると...

Kibana からスロークエリログが確認出来るようになる。

f:id:inokara:20161022145136p:plain

マッピングは適宜設定する必要がある。

以上

  • メモでした
  • 久し振りに Logstash を触った(改めて勉強し直したい)

2016 年 10 月 22 日(土)

fitbit charge2

fitbit charge2 が欲しい。fitbit charge2 経由で収集したデータが JSON で取れるのが面白そう。

dev.fitbit.com

読書

Python チュートリアル 第 3 版

  • 第二章、三章

夕食

  • 自宅で焼き肉
  • かぼちゃが美味しかった
  • 後片付けが大変

logstash

アウトプットプラグイン