ujunのブログ

AWS Systems Manager でタスクのスケジューリングを置き換えれる

SSMとCloudWatch Eventで置き換えられそう。

Terraform の 以下を参考にする

AWS: aws_cloudwatch_event_target - Terraform by HashiCorp

resource "aws_iam_role" "ecs_events" {
  name = "ecs_events"
  assume_role_policy = <<DOC
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
DOC
}

resource "aws_iam_role_policy" "ecs_events_run_task_with_any_role" {
  name = "ecs_events_run_task_with_any_role"
  role = "${aws_iam_role.ecs_events.id}"
  policy = <<DOC
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "ecs:RunTask",
            "Resource": "${replace(aws_ecs_task_definition.task_name.arn, "/:\\d+$/", ":*")}"
        }
    ]
}
DOC
}

resource "aws_cloudwatch_event_target" "ecs_scheduled_task" {
  target_id = "run-scheduled-task-every-hour"
  arn       = "${aws_ecs_cluster.cluster_name.arn}"
  rule      = "${aws_cloudwatch_event_rule.every_hour.name}"
  role_arn  = "${aws_iam_role.ecs_events.arn}"

  ecs_target = {
    task_count = 1
    task_definition_arn = "${aws_ecs_task_definition.task_name.arn}"
  }

  input = <<DOC
{
  "containerOverrides": [
    {
      "name": "name-of-container-to-override",
      "command": ["bin/console", "scheduled-task"]
    }
  ]
}
DOC
}

Relational DBMS Internals - Ch.2

最近読んでいるので、2章のまとめ。

http://pages.di.unipi.it/ghelli/bd2/DBMS-Internals.pdf

  • Permanent Memory And Buffer Management
    • DBMSを実装する上で最初に解決するべきは、システムを構成する複数のコンポーネントに依存せずストレージを抽象的に扱うことのできるレイヤの提供である
      • Permanent Memory ManagerとBuffer Managerによって実現される
    • 記憶領域の種類
      • Main memory
      • Permanent memory with magnetic disks
      • Temporary memory with NAND flash memory
    • Permanent Memory Manager
      • diskを抽象化するレイヤを提供する
      • 各DBを物理ファイル(物理ページ)の集合で表現する
      • DBとは実データやインデックスの物理ページを含んだディレクトリである
      • 各レコードは実ファイル上に構成される論理ファイルとなる
    • Buffer Manager
      • Permanent Memory のページをMain memoryと対応される役割
      • トランザクションが必要としているページを取得するのに責任を持つ
      • クエリのパフォーマンスはTemporary memory に読み込むページの数に依存する
      • 以下BufferManagerの構成要素
        • The buffer pool
          • Permanen memory のコピーやその保持情報を格納するArray
          • 固定サイズなので、適切なアルゴリズムにしたがってfreeして空き容量を確保する必要がある
          • Arrayの各要素は以下の変数を持つ (最初はすべての要素のpin countは0, dirtyはfalse)
            • the pin count(その要素が含むページが現在参照されている数)
            • dirty(そのページが、buffer poolに読み込まれて以降に更新があったか)
        • A hash resident pages table
          • permanent memeory pageがbuffer poolにのっているかとその対応するArrayの要素を管理する
      • Buffer Managerは以下のようなインタフェースを提供する
        • getAndPinPage(P)
          • P(ページ)がArrayの要素になっていれば、そのpin countをインクリメントする
            • Pのidを返す
          • PがArrayの要素になっていなければ
            • freeなArrayの要素(pin countは0)をgetする(LRUなどアルゴリズムはいくつかある).
              • ただしgetした要素がdirtyであればflushする(例えば物理ページにwrite outするなどして).
              • Arrayに空きがなければexception
            • getした要素にページを読み込んでpin countをインクリメントする(つまり1). dirtyはfalseとする.
            • BufferManagerはその要素のpin countが0にならないかぎり他のページをその要素にロードすることはない
            • resident table の情報をupdateする
              • 古いページに関する情報をdelete
              • 新しいページに関する情報をinsert
        • setDirty(P)
          • requesterがPを更新するときこれを呼ぶ. BufferManagerにArrayの対応する要素のdirtyをTrueにするよう指示する
        • unpinPage(P)
          • requesterがもはやPを必要としない時これを呼ぶ. BufferManagerはこのArray要素のpin countをデクリメントする
        • flushPage(P)
          • requesterがPをpermanent memoryに書き出したいときにこれを呼ぶ.
          • PがdirtyであったらBufferManagerはPをpermanent memoryに書く.
      • unpinしたりflushしたりするのはrequesterではなくTransaction and Recovery Managerによって行われる.
        • requesterはリクエストを出すのみ.

SQSのキューからひたすらreceiveするくん

60万件くらい入ってたSQSのキューからひたすらreceiveした人

10多重

package main

import ( 
    "sync"
    "fmt"
    "github.com/aws/aws-sdk-go/service/sqs"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    kingpin "gopkg.in/alecthomas/kingpin.v2"
)

var (
    profile       = kingpin.Flag("profile", "AWS credential(default profile if not specified)").Default("").String()
    region        = kingpin.Flag("region", "AWS region(ap-northeast-1 if not specified)").Default("ap-northeast-1").String()
)

func main() {
    kingpin.Parse() 

    // session for AWS
    sess := session.Must(session.NewSessionWithOptions(session.Options{Profile:*profile, Config:aws.Config{Region:region}}))
    
    // sqs client
    sqs_svc := sqs.New(sess)
    
    wg := &sync.WaitGroup{}
    receiveMessageInput := sqs.ReceiveMessageInput{}
    receiveMessageInput.SetMaxNumberOfMessages(10).SetQueueUrl("url")
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func () {
            for {
                receiveMessageOutput, _ := sqs_svc.ReceiveMessage(&receiveMessageInput)
                if len(receiveMessageOutput.Messages) == 0 { break }
                for _, mess := range receiveMessageOutput.Messages {
                    fmt.Println(*mess.Body)
                    deleteMessageInput := sqs.DeleteMessageInput{}
                    deleteMessageInput.SetReceiptHandle(*mess.ReceiptHandle).SetQueueUrl("url")
                    _, err := sqs_svc.DeleteMessage(&deleteMessageInput)
                    if err != nil {
                        panic(err)
                    }
                }
            }
            wg.Done()
        }()
    } 
    wg.Wait()
}    
 

packer buildしてできたamiだけ取得(packerの出力変わると使えない)

#!/usr/bin/env python

import subprocess
import re

def run(cmd):
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        if not line and p.poll() is not None:
            for text in stdout:
                match = re.search('ap-northeast-1:\s{1}(.*)', str(text.decode("utf-8")))
                if match:
                    print(match.group(1), end = "")
            break
    return ''.join(str(stdout))

if __name__ == '__main__':
    run("packer build -var-file=var.json template.json") 

プレースホルダを含んだtfを使うなど

python exec_packer.py | xargs -I{} sed -e 's/{{AMI_ID}}/{}/g' test.tf > test_e.tf

Executable JAR を作る

Treasure Data の Embulk(http://www.embulk.org/docs/) をインストールして気になったので、メモ。

Embulkがなんなのかはドキュメントを読むとして、インストールするには以下のコマンドを叩くだけではいった。

curl --create-dirs -o ~/.embulk/bin/embulk -L "https://dl.embulk.org/embulk-latest.jar"
chmod +x ~/.embulk/bin/embulk
echo 'export PATH="$HOME/.embulk/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

これはjarをローカルに保存してそこにパスを通してるだけなんだけど、jarを直接指定して実行できるというのが妙にかっこいい。

普通jarファイルを実行するときは、 java -jar hoge.jar みたいな感じですると思う。

でも、以下の方法でexecutableな jarを作成することができる。

coderwall.com

手順としては、

#!/bin/sh
MYSELF=`which "$0" 2>/dev/null`
[ $? -gt 0 -a -f "$0" ] && MYSELF="./$0"
java=java
if test -n "$JAVA_HOME"; then
    java="$JAVA_HOME/bin/java"
fi
exec "$java" $java_args -jar $MYSELF "$@"
exit 1

を、executableにしたいjarの先頭に追加するだけ。

上記をstub.shなどとして保存して、 cat stub.sh hoge.jar > hoge.run とすると良い。

ちなみに、embulkのjarは、その先頭の END_OF_EMBULK_SELFRUN_BATCH_PART というセクションにMS-DOSのコマンドも書いてあり、 さすがですねと思った。

PackerでAMIを作ると流れるようにできるしChefの確認も流れるようにできた

HashiCorpのPackerの使い勝手の良さはすごくいい。

AWS環境でしか使っていないが、とても手軽にAMIをbakeできる。(bakeという単語を公式で使っていた)。 特に、Windows機のAMIをbakeするのに威力を発揮してくれた。

そもそも、Windowsに限らずだが、後のインスタンス構築の手間を省く目的でpre-bakedなAMIを作るとき、 本当にとっかかりの段階ではだいたい以下のようなサイクルを手元で回したくなるはずである(自分だけ?)。

Chefなどのプロビジョニングツールのレシピを書く
↓
( Chef Serverにレシピをupload ) 
↓
EC2インスタンス再作成して適用 
↓
失敗。。。調査。。
↓
Chefなどのプロビジョニングツールのレシピを書く
↓
( Chef Serverにレシピをupload ) 
↓
EC2インスタンス再作成して適用 
↓
失敗。。。調査。。
↓
・・・
・・・
↓
成功
↓
AMI化して完了

この時、Windowsだと圧倒的に困るのが、とにかくterminate/startが遅いことで、インスタンス再作成だけで数分とか数十分とか必要なことも。 せめて、terminateくらいは素早くやらせてほしい。

すばやく環境を構築/破棄するという観点では、Terraformを使っていると環境の構築/破棄がコマンド1つでとても簡単に行えるが、それでもapplyとdestroyを繰り返すのは、ずっと続けているとなんかアホらしくなってくる。

あと、applyしている途中で誤りに気づいてCtrl-cとかしたくても怖くてできない。 だいたい、applyのログ追ってれば、こりゃダメだって途中で気づくことも経験上よくある。しかし、Ctrl-cしたくてもterraformだと少し怖いし変にリソースが残るのが嫌なので、それはできない。

Packerだとこうだ。

簡単なtemplate(example.json)を用意する。

{
  "variables":{
    "home": "{{env `HOME`}}"
  },
  "builders": [
    {
      "type": "amazon-ebs",
      "region": "ap-northeast-1",
      "access_key": "{{user `aws_access_key`}}",
      "secret_key": "{{user `aws_secret_key`}}",
      "source_ami": "ami-d34f47b4",
      "instance_type": "t2.small",
      "ami_name": "hoge {{timestamp}}",
      "communicator": "winrm",
      "winrm_username": "Administrator"
    }
  ],
  "provisioners": [
    {
      "guest_os_type": "windows",
      "type": "chef-client",
      "server_url": "https://chef-server",
      "chef_environment": "production",
      "run_list": "recipe[example]",
      "encrypted_data_bag_secret_path": "/path/to/secret",
      "validation_client_name": "client",
      "validation_key_path": "/path/to/key"
    },
    {
      "type": "powershell",
      "inline": [
        "C:\\PROGRA~1\\Amazon\\Ec2ConfigService\\Ec2Config.exe -sysprep"
      ]
    }
  ]
}

これで、 packer build example.json を実行する。

Packer Builderというなぞのインスタンスがおもむろに立ち上がり、それに対しprovisionerが適用されていく。

よくあることだが作成途中にダメだと気づいたら、Ctrl-cすれば以下のようにちゃんと後始末してくれる。そしたらレシピを修正するなりpackerのログを見るなりして、またbuildすればよい。

・・・
^C==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
==> amazon-ebs: Deleting temporary security group...
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.
Cleanly cancelled builds after being interrupted.

terraformとの些細な違いは、自分でdestroyしなくて済むということだが、試行錯誤してる時には、これが体感として非常に利いてくる気がしている。 なんというか、カジュアルさが違う。terraformのように重おもしくない。

さらに、packerには、このように環境構築に対する圧倒的カジュアルさのほかに、非常に便利な機能がある。 それが、ステップ実行である。

packer build -debug example.json だけでそれは起動する。

まさにコードをデバッグするときのあの挙動そのままであり、breakpointは打てないが、各種provisionerとかoptionとかの実行直前でbreakしてくれて、 プロンプトが返るようになっている。

break中に例えば対象のインスタンスssh/rdpして中を確認したりできる。あたかも、コード実行中にデバッガで特定の変数の値を確認しているかのよう。 ターミナルに戻ってEnterすれば後続の処理がされていく。

ちなみにこの時点でCtrl-cしても、やはり先ほど同様後始末して終わってくれる。使いやすさ。

このように、Packerには、個別のインスタンスを作ってプロビジョニングして壊すのに最適な機能がたくさんあることに最近気づいた。

いままで考えもしなかったけど、 Packerは手元でのユニットテストに、TerraformはbakeしたAMIやその他クラウドリソース等の結合テストにというように、テスト工程に無理やりマッピングすると意外としっくりくるように思う。

そう考えると、それぞれにそれぞれの役割の機能がちゃんと備わっているように見えてきた。

すごいぜ、HashiCorp。

しかし、軽くググっても、Chef Server、しかもPackerとかTerraform + Cher Serverという例が全然ないよな。 そもそもChef Serverって世の中的に使われてるのかな。。

ALBログのためのInput Plugin

ALB (https://aws.amazon.com/jp/blogs/aws/new-aws-application-load-balancer/) がリリースされてから半年以上経っているけれど、 明示的にサポートしているfluendプラグインがあまりないようなので書くことにした。

先人には、id:yomon8 さんのここのような記事があったけど、 自分の環境ではこれではなく以下のプラグインをずっと使っているため、こちらを修正することになる。

GitHub - winebarrel/fluent-plugin-elb-access-log: Fluentd input plugin for AWS ELB Access Logs.

とにかくテストが書けていないのであれだけど、一応動くものとしては以下のforkにpushしている。

github.com

ポイントとしては、id:yomon8 さんのを参考に、multiple_files_gzip_reader を使っている点だ。 あと、CLBとALBではログのフォーマットが変わっているのでそこの細かい対応をしている。

テストまで書ければ完璧なのだけど、pluginディレクトリに置いて起動し問題なく稼働しているしとりあえずOK。

簡単な修正で済んだ。