ようへいの日々精進XP

よかろうもん

AWS CLI で EIP を付けて、ひっぺがして、また付けて

tl;dr

インスタンスに EIP を付与して、インスタンスからひっぺがして別のインスタンスに付与するまで。

メモ

EIP 払い出し

  • run
ALLOCATION_ID=$(aws \
  --region ap-northeast-1 \
  ec2  allocate-address \
    --domain vpc \
    --query AllocationId \
    --output text) && echo ${ALLOCATION_ID}
  • output
eipalloc-xxxxxxxx

IP アドレスを確認

  • run
EIP=$(aws \
  --region ap-northeast-1 \
  ec2  describe-addresses \
    --allocation-ids ${ALLOCATION_ID} \
    --query Addresses[].PublicIp \
    --output text) && echo ${EIP}
  • output
xx.xxx.xx.xxx

EIP 付与

  • run
REGION=ap-northeast-1
INSTANCE_ID=$(aws \
--region ${REGION} \
ec2 describe-instances \
  --filters Name=tag:Name,Values=foo \
  --query 'Reservations[].Instances[].InstanceId' \
  --output text) && echo ${INSTANCE_ID}

ASSOCIATE_ID=$(aws \
  --region ${REGION} \
  ec2 associate-address \
    --instance-id ${INSTANCE_ID} \
    --allocation-id ${ALLOCATION_ID} \
    --output text) && echo ${ASSOCIATE_ID}

aws \
  --region ${REGION} \
  ec2 describe-instances \
    --filters 'Name=tag:Name,Values=foo' \
    --query 'Reservations[].Instances[].[Tags[?Key==`Name`] | [0].Value,PublicIpAddress]' \
    --output table
  • output
i-xxxxxxxx
eipassoc-87654321
--------------------------
|    DescribeInstances   |
+------+-----------------+
|  foo |  xx.xxx.xx.xxx  |
+------+-----------------+

EIP ひっぺがし

  • run
ENI=$(aws \
  --region ${REGION} \
  ec2 describe-instances \
    --instance-ids ${INSTANCE_ID} \
    --query 'Reservations[].Instances[].NetworkInterfaces[].NetworkInterfaceId' \
    --output text) && echo ${ENI}

ASSOCIATE_ID=$(aws \
  --region ${REGION} \
  ec2 describe-network-interfaces \
    --network-interface-ids ${ENI} \
    --query NetworkInterfaces[].Association[].AssociationId \
    --output text) && echo ${ASSOCIATE_ID}

aws --region ${REGION} ec2 disassociate-address --association-id ${ASSOCIATE_ID}
  • output
eni-xxxxxxxx
eipassoc-xxxxxxxx

EIP を別のインスタンスに付与

  • run
REGION=ap-northeast-1
ALLOCATION_ID=$(aws \
  ec2 describe-addresses \
    --public-ips ${EIP} \
    --query Addresses[].AllocationId \
    --output text) && echo ${ALLOCATION_ID}

NEW_INSTANCE_ID=$(aws \
  --region ${REGION} \
  ec2 describe-instances \
    --filters Name=tag:Name,Values=bar \
    --query 'Reservations[].Instances[].InstanceId' \
    --output text) && echo ${NEW_INSTANCE_ID}

NEW_ASSOCIATE_ID=$(aws \
  --region ${REGION} \
  ec2 associate-address \
    --instance-id ${NEW_INSTANCE_ID} \
    --allocation-id ${ALLOCATION_ID} \
    --query AssociationId \
    --output text) && echo ${NEW_ASSOCIATE_ID}

aws \
  --region ${REGION} \
  ec2 describe-instances \
    --filters 'Name=tag:Name,Values=bar' \
    --query 'Reservations[].Instances[].[Tags[?Key==`Name`] | [0].Value,PublicIpAddress]' \
    --output table
  • output
eipalloc-zzzzzzzz
i-zzzzzzzz
eipassoc-12345678
--------------------------
|    DescribeInstances   |
+------+-----------------+
|  bar |  xx.xxx.xx.xxx  |
+------+-----------------+

以上

メモでした。

logstash のアウトプットプラグイン作成チュートリアル

tl;dr

logstash を理解するあたってプラグイン作ってみることにした。

参考

www.elastic.co

github.com

雑なうんちく

logstash プラグインの種類は?

実装は?

雛形生成

プラグイン作成にあたって雛形を生成するコマンドが用意されている。

/opt/logstash/bin/logstash-plugin generate --type input --name oreno_input --path /path/to/plugins/
/opt/logstash/bin/logstash-plugin generate --type output --name oreno_output --path /path/to/plugins/
/opt/logstash/bin/logstash-plugin generate --type filter --name oreno_filter --path /path/to/plugins/
/opt/logstash/bin/logstash-plugin generate --type codec --name oreno_codec --path /path/to/plugins/

アウトプットプラグインチュートリアル

何を作るか

Mackerel にデータを飛ばすプラグインを作ってみる。

実装、検証の環境

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"

$ java -version
openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-8u91-b14-3ubuntu1~16.04.1-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)

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

雛形作成

/opt/logstash/bin/logstash-plugin generate --type output --name mackerel --path ./
 Creating ./logstash-output-mackerel
         create logstash-output-mackerel/logstash-output-mackerel.gemspec
         create logstash-output-mackerel/Rakefile
         create logstash-output-mackerel/DEVELOPER.md
         create logstash-output-mackerel/CONTRIBUTORS
         create logstash-output-mackerel/CHANGELOG.md
         create logstash-output-mackerel/LICENSE
         create logstash-output-mackerel/spec/outputs/mackerel_spec.rb
         create logstash-output-mackerel/lib/logstash/outputs/mackerel.rb
         create logstash-output-mackerel/Gemfile
         create logstash-output-mackerel/README.md

logstash-output-mackerel.gemspec を修正

プラグインの詳細や依存する Gem を記載する。

--- /tmp/logstash-output-mackerel/logstash-output-mackerel.gemspec      2016-10-23 08:34:50.927361142 +0900
+++ logstash-output-mackerel.gemspec    2016-10-23 08:37:28.224957272 +0900
@@ -1,24 +1,25 @@
 Gem::Specification.new do |s|
-  s.name          = 'logstash-output-mackerel'
-  s.version       = '0.1.0'
-  s.licenses      = ['Apache License (2.0)']
-  s.summary       = 'TODO: Write a short summary, because Rubygems requires one.'
-  s.description   = 'TODO: Write a longer description or delete this line.'
-  s.homepage      = 'TODO: Put your plugin''s website or public repo URL here.'
-  s.authors       = ['']
-  s.email         = 'inokara@gmail.com'
-  s.require_paths = ['lib']
+  s.name = 'logstash-output-mackerel'
+  s.version         = "0.0.1"
+  s.licenses = ["Apache License (2.0)"]
+  s.summary = "foo."
+  s.description     = "bar."
+  s.authors = ["kappa"]
+  s.email = "inokara@example.com"
+  s.homepage = "http://example.com"
+  s.require_paths = ["lib"]

(snip)

   # Gem dependencies
-  s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
-  s.add_runtime_dependency "logstash-codec-plain"
+  s.add_runtime_dependency "logstash-core", ">= 2.0.0", "< 3.0.0"
+  s.add_runtime_dependency "mackerel-client"
   s.add_development_dependency "logstash-devutils"
+  s.add_development_dependency "mackerel-client"

lib/logstash/outputs/mackerel.rb に処理を書く

エラー処理等はひとまず無し。

# encoding: utf-8
require "logstash/outputs/base"
require "logstash/namespace"

class LogStash::Outputs::Mackerel < LogStash::Outputs::Base
  config_name "mackerel"
  config :api_key, :validate => :string, :required => true
  config :service_name, :validate => :string, :required => true

  public
  def register
    require 'mackerel/client'
    @mackerel = Mackerel::Client.new(:mackerel_api_key => @api_key)
  end

  public
  def receive(event)
    metrics = [{'name' => event.get('host'), 'time' => event.get('timestamp').to_i, 'value' => event.get('value')}]
    @mackerel.post_service_metrics(@service_name, metrics)
  end
end
  • config_name にはプラグイン名を定義
  • config には logstash の設定ファイル内に記載するパラメータやパラメータのタイプを記載(今回は api_keyservice_name を定義)
  • register メソッドは initialize メソッドのように利用する(今回は Mackerel への接続クライアントを作成している)
  • receive メソッドは event に含まれているデータを次の処理に渡す(今回は mackerel-client-rubypost_service_metrics メソッドを利用して Mackerel にデータをポストする)

尚、receive メソッドに渡される引数 eventLogStash::Event というクラスとなっているので、各要素については以下のように get メソッドを利用した。

metrics = [{'name' => event.get('host'), 'time' => event.get('timestamp').to_i, 'value' => event.get('value')}]

Gem をビルドする

cd logstash-output-mackerel
gem build logstash-output-mackerel.gemspec

カレントディレクトリに gem ファイルが生成される。

$ ls -l *.gem
-rw-rw-r-- 1 kappa kappa 7680 1023 08:54 logstash-output-mackerel-0.0.1.gem

ビルドした Gem をインストール

cd logstash-output-mackerel
sudo /opt/logstash/bin/plugin install logstash-output-mackerel-0.0.1.gem

動かしてみる

logstash 設定

input {
  exec {
      #
      # /tmp/test.sh
      # echo $(date +%s) ${RANDOM}
      # 
      # 60 秒毎に /tmp/test.sh を実行する
      #
      command => "bash /tmp/test.sh"
      interval => 60
  }
}

filter {
  grok {
    # 
    match => [ "message", "%{NUMBER:timestamp}\s*%{NUMBER:value:int}"]
  }
  date {
    match => [ "timestamp", "UNIX" ]
  }
}

output {
  stdout { codec => rubydebug }
  mackerel { 
    api_key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    service_name => 'hogehoge'
  }
}

今回は設定ファイル名を demo.conf として保存しておく。

実行

以下のように logstash を実行する。

/opt/logstash/bin/logstash -f demo.conf

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

Settings: Default pipeline workers: 4
Pipeline main started
{
       "message" => "1477181478 7945\n",
      "@version" => "1",
    "@timestamp" => "2016-10-23T00:11:18.000Z",
          "host" => "tpX1-Carbon",
       "command" => "bash /tmp/test.sh",
     "timestamp" => "1477181478",
         "value" => 7945
}
{
       "message" => "1477181538 23429\n",
      "@version" => "1",
    "@timestamp" => "2016-10-23T00:12:18.000Z",
          "host" => "tpX1-Carbon",
       "command" => "bash /tmp/test.sh",
     "timestamp" => "1477181538",
         "value" => 23429
}

しばらく放置しておくと...

f:id:inokara:20161023091514p:plain

よし。

以上

  • 若干だけど logstash の挙動を理解することが出来た
  • 思ったりよりも簡単にプラグインを実装することが出来た

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 を触った(改めて勉強し直したい)

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 を叩かせないということは出来なさそう)

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:

ラムダこりゃ外伝 - 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\"}"

おけ。

以上

メモでした。

Azure AD で簡単な Web アプリにパスワードシングルサインオンしてみるよ

tl;dr

Azure AD でログインフォームだけの Web アプリケーションを作ってシングルサインオンを試してみたのでメモ。用語の使い方や認識の誤り等あればご指摘いただければ幸いですmm

memo

参考

ありがとうございます。

Password-based single sign-on と Federation-based single sign-on について

こちらより引用。

  • Password-based single sign-on
Plug-in (extension) をブラウザーにインストールすることで、パスワードなどの入力を自動化し、シングル・サインオン (SSO) をおこなう方法です。
Azure AD は各ユーザーの情報を SaaS アプリにセットアップし、パスワードは Azure AD 側で一括で管理します
  • Federation-based single sign-on
いわゆる SAML などの標準プロトコルを使ってフェデレーションをおこなう方法です。Plug-in のインストールも不要です。

構成

今回は以下のような構成で試す。

f:id:inokara:20161016232650p:plain

Web アプリケーション

は Heorku にアップロード済み。

github.com

#
# アプリケーション作成
#
$ heroku create oreno-app
Creating ⬢ oreno-app... done
https://oreno-app.herokuapp.com/ | https://git.heroku.com/oreno-app.git

#
# heroku に push
#
$ git push heroku master
Counting objects: 33, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (32/32), done.
Writing objects: 100% (33/33), 3.49 KiB | 0 bytes/s, done.
Total 33 (delta 11), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Ruby app detected

(snip)

remote:        https://oreno-app.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/oreno-app.git
 * [new branch]      master -> master

実際に URL にアクセスすると以下のようなトップページ。

f:id:inokara:20161016213345p:plain

適切なユーザー名とパスワードを入力すると以下のようなあたかもマイページのようなページにアクセスする。

f:id:inokara:20161016213401p:plain

Azure AD Premium を有効にする(必要があった)

Web アプリケーションをアクセスパネルにに登録するよ

ここからはほとんどスクショで。

f:id:inokara:20161016223839p:plain

  • 「アプリケーション」タブを選択、[追加」を選択

f:id:inokara:20161016223852p:plain

  • 「ギャラリーからアプリケーションを追加します」を選択

f:id:inokara:20161016223901p:plain

  • 「カスタム」→「私の組織で使用している、一覧になりアプリケーションを追加」→「名前」にアプリケーション名を入力

f:id:inokara:20161016223910p:plain

f:id:inokara:20161016223920p:plain

f:id:inokara:20161016223929p:plain

  • 「サインオン URL」に Web アプリケーションの URL を入力(ログイン画面の URL を入力)

f:id:inokara:20161016223937p:plain

  • 設定完了

f:id:inokara:20161016223948p:plain

  • 「ユーザーとグループ」からシングルサインオンを割り当てたいユーザーを選択し、「割り当て」を選択

f:id:inokara:20161016223959p:plain

  • Web アプリケーションの認証情報を入力する(今回はユーザー名:foo とパスワード:foo01 を入力する)

f:id:inokara:20161016224008p:plain

f:id:inokara:20161016224022p:plain

  • Extension のインストール後、改めてアクセスパネルにアクセスすると以下のような状態となり、登録された Web アプリケーションのパネルをクリックすると...

f:id:inokara:20161016224032p:plain

  • パスワードが自動的に入力され、Web アプリケーションにログインすることが出来た

f:id:inokara:20161016224045p:plain

以上

  • 今更だけどシングルサインオン便利
  • Azure AD の実力をほんの少し垣間見れた気がした
  • Heroku が簡単過ぎてビックリした

collectd と facette の Vagrantfile を作った(collectd でサバクラ環境を作る)

tl;dr

  • マイブームの collectd でサーバー、クライアント環境って作れるのか調べてみたら意外に簡単だった
  • facette もセットアップは簡単なのでついでに触ってみる

メモ

Vagrantfile

github.com

collectd のクラサバ構成

クラサバ構成のキモは network プラグイン。以下のように server(メトリクスデータを収集する側)、client(サーバーにメトリクスデータを飛ばす側)で設定するだけ。

#
# server
#
$ cat /etc/collectd/collectd.conf.d/network.conf
LoadPlugin network

<Plugin network>
  <Listen "0.0.0.0" "25826">
    # SecurityLevel Sign
    # AuthFile "/etc/collectd/passwd"
    # Interface "eth0"
  </Listen>
</Plugin>

#
# client
#
$ cat /etc/collectd/collectd.conf.d/network.conf
LoadPlugin network

<Plugin network>
  <Listen "0.0.0.0" "25826">
    # SecurityLevel Sign
    # AuthFile "/etc/collectd/passwd"
    # Interface "eth0"
  </Listen>
</Plugin>

認証も設けることが出来るので、実運用の際には認証は設定しておきたい。(上記の Vagrantfile では認証は設定されていない)

facette とは

facette は Go 言語で書かれた rrd や Graphite 等の時系列データベースをバックエンドデータベースとして利用可能な時系列データの可視化ツール。

facette.io

現在は開発途上な為、プロダクション環境での利用は推奨されていないので注意。

導入については、各種 OS 環境のパッケージを取得してインストールする。

vagrant up

vagrant up --provision

実行すると仮想マシンが 2 台構築されて以下のような構成となる。

[ vm02 ](collectd) => [ vm01 ](facette / collectd) <= [ ワシ ] 

facette UI

ざっくりと紹介。

f:id:inokara:20161010182833p:plain

Graphite とか influxdb とかも利用可能。機会があれば試してみよう。

f:id:inokara:20161010182913p:plain

Origin がバックエンドのデータベースのことだと思われる。今回は collectd(rrd)のみ。

f:id:inokara:20161010182954p:plain

Sources が各 collectd が動いているホスト。Source Groups で各 Source をグルーピング出来る。

f:id:inokara:20161010183001p:plain

collectd で収集しているメトリクス各種。

f:id:inokara:20161010183011p:plain

まだまだお触りが足りていないけど、グラフをこさえるところまではイケた。

以上

メモでした。

collectd の exec プラグインで俺のデータを CloudWatch に飛ばす

全国 5000 万人の収集癖、メトリクス厨の皆さん、こんばんわ。かっぱです。

tl;dr

の続き。

collectd はプラグイン機構が備わっており、自作することも出来る。

ただ、コマンドの標準出力の結果をメトリクスとして記録してくれる exec プラグインを利用することで、より簡単にオリジナルデータを collectd に飛ばすことが出来るようだ。

参考

memo

引続き、試している環境は...

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"

exec プラグインスクリプト実装時の注意点

以下のようなフォーマットで標準出力させることで由なに collectd が集めてくれる。

"PUTVAL #{hostname}/#{PLUGIN_NAME}/#{metric_name} #{unix_time}:#{data}"

また、以下のようにオプションとして interval=#{INTERVAL} も指定可能。

"PUTVAL #{hostname}/#{PLUGIN_NAME}/#{metric_name} interval=#{INTERVAL} #{unix_time}:#{data}"

例えば、ランダムな数値を収集したい場合に Ruby では以下のように書ける。

#!/usr/bin/env ruby

PLUGIN_NAME = 'oreno-collectd-plugin'
hostname    = ENV['COLLECTD_HOSTNAME'] || "localhost"
interval    = ENV['COLLECTD_INTERVAL'] || 20

def usage
  puts("#{$0} -h <host_id> [-i <sampling_interval>]")
  exit
end

# Main
begin
  $stdout.sync = true

  while true do
    start_run = Time.now.to_i

    random = Random.new
    data = random.rand(1000..10000)
    puts("PUTVAL #{hostname}/#{PLUGIN_NAME}/gauge-random-number #{start_run}:#{data}")

    sleep(interval.to_i)
  end
end

このプログラムを実行すると、以下のように出力される。

$ ./oreno-collectd-plugin.rb
PUTVAL localhost/oreno-collectd-plugin/gauge-random-number 1475923480:4336
PUTVAL localhost/oreno-collectd-plugin/gauge-random-number 1475923500:4618
PUTVAL localhost/oreno-collectd-plugin/gauge-random-number 1475923520:8226
PUTVAL localhost/oreno-collectd-plugin/gauge-random-number 1475923540:5802
PUTVAL localhost/oreno-collectd-plugin/gauge-random-number 1475923560:9781

exec プラグインの設定

上記のスクリプトを /usr/lib/collectd/ 以下に置いて、実行権限を付与する。

sudo cp oreno-collectd-plugin.rb /usr/lib/collectd
sudo chmod +x oreno-collectd-plugin.rb

そして、/etc/collectd/collectd.conf.d/ に以下のような設定ファイルを置く。設定ファイル名は任意。ただ、拡張子に .conf が必要かもしれないので注意する。(ちゃんと確認出来ていない)

LoadPlugin exec

<Plugin exec>
  Exec vagrant "/usr/lib/collectd/oreno-collectd-plugin.rb"
</Plugin>

上記の設定が終わったら collectd を再起動する。

sudo service collectd restart

暫くすると以下のように rrd 形式でデータが保存される。

/var/lib/collectd/rrd/vagrant-ubuntu-trusty-64/oreno-collectd-plugin/gauge-random-number.rrd

CloudWatch プラグインの設定

前回同様に CloudWatch に飛ばしたいメトリクス名を以下のように whitelist.conf に追記する。

oreno-collectd-plugin-gauge-random-number

記述フォーマットは多分、以下のようなルールで正規表現も利用出来る。

#{プラグイン名}-#{メトリクス名}

暫くすると...

f:id:inokara:20161008200541p:plain

おお。

ちなみに...

collectd のフロントエンドとして Go で書かれた Facette でもメトリクスを表示させてみた。

f:id:inokara:20161008200752p:plain

Facette については、改めて書くことにする。

ということで

collectd 面白い。

CloudWatch collectd plugin メモ(EC2 以外の環境からメトリクスを飛ばす)

全国 5000 万人の収集癖の皆さん、こんばんわ。かっぱです。

tl;dr

collectd というサーバー上の各種メトリクスを収集するオープンソースツールのプラグインとして CloudWatch Plugin が awslab よりリリースされたようなので触ってみたいと思う。せっかくなので EC2 ではなく、手元の Vagrant 環境で動いている Ubuntu で動かしてみたい。

memo

試した環境

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"

IAM ユーザーの作成

マネジメントコンソールより IAM ユーザーを作成。以下のようなポリシーを付与。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "cloudwatch:PutMetricData"
      ],
      "Effect": "Allow",
      "Resource": [
        "*"
      ]
    }
  ]
}

アクセスキーとシークレットアクセスキーを控えておく。

collectd をインストール

sudo apt-get update
sudo apt-get install collectd

CloudWatch Plugin のセットアップ

wget https://raw.githubusercontent.com/awslabs/collectd-cloudwatch/master/src/setup.py
chmod +x setup.py
sudo ./setup.py

セットアップウィザードを以下のように進めた。

vagrant@vagrant-ubuntu-trusty-64:~$ 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

AWS region could not be automatically detected. Cause:Cannot access metadata service. Cause: HTTPConnectionPool(host='169.254.169.254', port=80): Max retries exceeded with url: /latest/meta-data/placement/availability-zone/ (Caused by ReadTimeoutError("HTTPConnectionPool(host='169.254.169.254', port=80): Read timed out. (read timeout=0.5)",))
Enter one of the available regions from: http://docs.aws.amazon.com/general/latest/gr/rande.html#cw_region
Enter region: ap-northeast-1

EC2 instance id could not be automatically detected.
Enter hostname [vagrant-ubuntu-trusty-64]:

IAM Role could not be automatically detected.
Enter absolute path to AWS credentials file [/home/vagrant/.aws/credentials]:
Enter access key: AKxxxxxxxxxxxxxxxx
Enter secret key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Choose how to install CloudWatch plugin in collectd:
  1. Do not modify existing collectd configuration
  2. Add plugin to the existing configuration
Enter choice [2]:
Plugin configuration written successfully.
Credentials configuration written successfully.
Stopping collectd process ... OK
Starting collectd process ... OK

CloudWatch に飛ばしたいメトリクスを whitelist.conf に設定

$ cat /opt/collectd-plugins/cloudwatch/config/whitelist.conf
memory--memory-.*

ちなみに、blocked_metrics というファイルに自動的に収集されるメトリクスのリストが記載されていて、その中から CloudWatch に飛ばしたいメトリクスのみを whitelist.conf に転記するようだ。(全部飛ばしたらコスト的にも大変だと思う)

collectd を再起動

sudo service collectd restart

暫くすると...

f:id:inokara:20161008150024p:plain

カスタムメトリクスに登録された。おお。

最後に

カスタムメトリクスのコスト

CloudWatch にカスタムメトリックを飛ばしたい

  • と思った時に収集部分だけを実装すればいいことになる
  • collectd が収集するメトリクスは collectd-web のような Web インタフェースで見つつ、アラームを飛ばしたいメトリクスだけ CloudWatch に飛ばすというのもありかなーと思った

一応

  • EC2 以外でも使えるので、マルチクラウド環境の監視を一時的にでも CloudWatch に寄せたいようなニーズがあれば検討出来るかもしれない