全力で怠けたい

怠けるために全力を尽くしたいブログ。

iTerm2 のカレントのプロファイルは $ITERM_PROFILE で取得できる。

iTerm2 のカレントのプロファイルの取得方法のメモ。

iTerm2 のカレントのプロファイルの取得方法

iTerm2 のカレントのプロファイルは $ITERM_PROFILE で取得できる。

$ echo $ITERM_PROFILE
my-favorite-profile

カレントのプロファイルを取得できると何が嬉しいのか

iTerm2 のカレントのプロファイルを取得できると何が嬉しいのかというと用途に応じて iTerm2 のプロファイルを切り替えているのを元のプロファイルに戻したいときなどだ。

iTerm2 のプロファイルは echo "\033]1337;SetProfile=<プロファイル名>\a" で切り替えることができるので、SSH でログインするときにログイン先によってプロファイルを切り替えて、SSH 先からログアウトするときに元のプロファイルに戻すシェルスクリプトは以下のように書くことができる。

#!/bin/sh

CUR_PROFILE=$(echo ${ITERM_PROFILE})
echo "\033]1337;SetProfile=$1\a"
ssh "$@"
echo "\033]1337;SetProfile=${CUR_PROFILE}\a"

普段から iTerm2 の Default プロファイルを使っているとき

普段から iTerm2 の Default プロファイルを使っているときは元のプロファイルに戻すところは echo "\033]1337;SetProfile=Default\a" みたいに決め打ちでいいと思う。

Redash からダウンロードできる CSV ファイルの改行コードが CRLF だった。

Redash からダウンロードできる CSV ファイルの改行コードが CRLF なんだー、と思ったのでメモ。

書いた動機

Redash はクエリーの結果を CSV ファイルでダウンロードすることができるので CSV ファイルをいろいろ加工したりしてとても便利なのだが、Redash からダウンロードした CSV ファイルの改行コードが CRLF であることに気が付かず軽くハマった。

自分は忘れっぽいので時間がたつとまたハマりそうなので忘れないように書いておく。

Redash からダウンロードすることができる CSV の改行コードは CRLF

ダウンロードするものがないと始まらないので、おそらく日本の Redash ユーザーの7割くらいはお世話になっただろう kakakakakku さんの redash-hands-oncity テーブルからデータを引っ張ってみる。

こんな感じの雑な SQL で引っ張ってきたデータの CSV ファイルをダウンロードする。

select * from city limit 3;

で、おもむろに file コマンドで見てみると改行コードが CRLF であることが分かる。

$ file city.csv
city.csv: ASCII text, with CRLF line terminators

$ cat city.csv
ID,Name,CountryCode,District,Population
1,Kabul,AFG,Kabol,1780000
2,Qandahar,AFG,Qandahar,237500
3,Herat,AFG,Herat,186800

一見したところこのファイルは人畜無害なのだが、そのままだと CLI で軽く困ったりする。 たとえば、次のような単純なコマンドもおそらく意図しているとおりには動かない。

$ cat city.csv | xargs -I{} echo "**{}**"
**ID,Name,CountryCode,District,Population
**1,Kabul,AFG,Kabol,1780000
**2,Qandahar,AFG,Qandahar,237500
**3,Herat,AFG,Herat,186800

ではどうすればいいかというと、CR をとってしまうのが一番簡単 (たぶん)

$ cat city.csv | tr -d \\r  | xargs -I{} echo "**{}**"
**ID,Name,CountryCode,District,Population**
**1,Kabul,AFG,Kabol,1780000**
**2,Qandahar,AFG,Qandahar,237500**
**3,Herat,AFG,Herat,186800**

Redash からダウンロードする CSV ファイルの改行コードの設定とか Redash のどこかにありそうなものなんだけど、3分くらい探して見つからなかったので雑に CR を削除することにしたときのメモは以上。

どのアベイラビリティゾーン? のやりとりは AZ ID を使うべき理由。

昨日 (8/23) AWS の東京リージョンで大規模な障害が発生した。

piyolog.hatenadiary.jp

Twitter は結構な騒ぎになっていて、インフラと AWS をやり始めたばかりの自分も Service Health DashboardTwitter は結構頻繁にチェックしていたのだが「どうも障害が発生しているアベイラビリティゾーンは apne1-az4 らしい」というツイートを Twitter で見つけた。 apne1-az4 というのは AZ ID というものだということは分かったのだが「ap-northeast-1a みたいなやつとなにが違うのか?」と気になってちょっと調べてみたのをメモしておく。

メモ

とりあえず公式ドキュメント。

docs.aws.amazon.com

公式ドキュメントには以下のように書いてある。

アベイラビリティーゾーンは、リージョンコードとそれに続く文字識別子によって表されます (us-east-1a など)。リソースがリージョンの複数のアベイラビリティーゾーンに分散するように、アベイラビリティーゾーンは各 AWS アカウントの名前に個別にマップされます。たとえば、ご使用の AWS アカウントのアベイラビリティーゾーン us-east-1a は別の AWS アカウントのアベイラビリティーゾーン us-east-1a と同じ場所にはない可能性があります。

ようするにアカウント A の us-east-1a = アカウント B の us-east-1a が成り立つとは限らない、ということだ。

では、アカウント A とアカウント B との間で「障害が発生しているのはどのアベイラビリティゾーンか?」という情報を交換するにはどうすればよいかというと、ここで AZ ID の出番となる。 公式ドキュメントには以下のように書いてある。

アカウント間でアベイラビリティーゾーンを調整するには、アベイラビリティーゾーンの一意で一貫性のある識別子である AZ ID を使用する必要があります。たとえば、use1-az1 は、us-east-1 リージョンの AZ ID で、すべての AWS アカウントで同じ場所になります。

まとめると以下のようになる。

  • 単一アカウント内で情報をやりとりするときは us-east-1a のようなのでやり取りしていい。別に AZ ID を使ってもいい
  • 複数アカウント間で情報をやりとりするときは use1-az1 のような AZ ID でやりとりする必要がある

さいごに

有名サービスやゲームがバタバタと倒れていくなかうちのサービスはほとんど運営に支障はなかったようで大変幸運だった。

Redash のバックアップとリストアのメモ。

サービスの運営で Redash を使っていて運営メンバーは結構カジュアルにクエリとかいじってもらっているので、バックアップとリストアといった運用上必須なところをどうしているかをメモしておく。

はじめに

Redash は 公式の AMI を使って AWS 上に構築しているのでそれ前提で書く。 とはいっても 公式ドキュメント のバックアップ手順は sudo -u redash pg_dump redash | gzip > backup_filename.gz と書いてあるので、ようは Redash が内部で使っている PostgreSQL のバックアップを取得しておけばよい。 同じように、Redash をリストアするときは PostgresSQL のバックアップを Redash が内部で使っている PostgresSQL に流し込めばよい。

バックアップ

バックアップは以下のようなスクリプトを Docker ホスト (= Redash の AMI の EC2 インスタンス) の cron で動かしていて、gzip 圧縮した PostgreSQL のバックアップファイルは S3 へ保管している。

#!/bin/sh

# PostgreSQL のコンテナ ID を取得
CID=$(sudo docker container ls | grep redash_postgres | awk '{print $1}')

# PostgreSQL をバックアップ
sudo docker container exec ${CID} /bin/bash -c 'pg_dump -U postgres postgres | gzip > /usr/local/redash-backup.gz'

# PostgreSQL のバックアップファイルを PostgreSQL コンテナからホスト (EC2) へ持ってくる
sudo docker container cp ${CID}:/usr/local/redash-backup.gz redash-backup.gz

# S3 へ保管
aws s3 cp redash-backup.gz s3://<バケット名>

リストア

リストアは以下のようなスクリプトredash-restore.sh redash-backup.gz のように実行して、スクリプトがエラーなく終わればブラウザから Redash に接続して動作を確認する。

リストアの手順自体は難しいところはないけどそれなりにコマンドを打つし自分は絶対間違えるのでスクリプト化している。

#!/bin/sh

set -x

if [ $# -lt 1 ]; then
    echo 'usage: redash-restore.sh <backup>'
    exit 1
fi
BACKUP_PATH=$(readlink --canonicalize $1)
if [ ! -f ${BACKUP_PATH} ]; then
    echo "${BACKUP_PATH} not exists."
    exit 1
fi

# Redash 公式 AMI は設定ファイルやボリュームは /opt/redash/ にある
DOCKER_COMPOSE_YML=/opt/redash/docker-compose.yml

# Redash 絡みのコンテナを一旦止める
sudo docker-compose --file ${DOCKER_COMPOSE_YML} down --remove-orphans

# PostgreSQL のバックアップを流し込む先の PostgreSQL コンテナを動かす
sudo docker container run -d -v /opt/redash/postgres-data:/var/lib/postgresql/data -p 5432:5432 postgres:9.5.6-alpine

# PostgreSQL のバックアップファイルをホスト (EC2) から PostgreSQL コンテナへ持っていく
CID=$(sudo docker container ls | grep postgres | awk '{print $1}')
sudo docker container cp ${BACKUP_PATH} ${CID}:/usr/local/redash-backup.gz

# Redash のデータベースを削除 & 再作成
sudo docker container exec ${CID} /bin/bash -c 'psql -c "drop database if exists postgres" -U postgres template1'
sudo docker container exec ${CID} /bin/bash -c 'psql -c "create database postgres" -U postgres template1'

# バックアップを PostgreSQL へ流し込む
sudo docker container exec ${CID} /bin/bash -c 'zcat /usr/local/redash-backup.gz | psql -U postgres -d postgres'

# バックアップの流し込みに使った PostgreSQL のコンテナはもう使わないので止める
sudo docker container stop ${CID}
sudo docker container rm ${CID}

# Redash 絡みのコンテナを動かす
sudo docker-compose --file ${DOCKER_COMPOSE_YML} up --detach

参考ページ

まとめ

いまのところは Redash のクエリやダッシュボードのバックアップは PostgreSQL のバックアップで十分に事足りているが、バックアップとは別に Redash 特にクエリの変更履歴は管理したいなーと思っているので、そっちも調べて運用に取り込んでいきたい。

AWS Lambda を使って Github リポジトリのクローン数の草を Pixela に生やすメモ。

AWS Lambda を使って Github リポジトリのクローン数の草を Pixela に生やすメモ。

Github は Web UI や API を使ってリポジトリのクローン数を取得することができるのだが、どちらの方法を使っても過去2週間分のクローン数しか見られない。そこで、AWS Lambda を使って定期的に Github のクローン数を取得して Pixela に記録することにした。Pixela は操作が簡単な上に草を生やすことができるのでビジュアル面でも Github のクローン数を記録するという用途にぴったりだ。

実際に pixela-client-go のクローン数を Pixela に記録してみるとこんな感じになる。

AWS Lambda を使って Github リポジトリのクローン数の草を Pixela に生やすまでにしたことをメモしておく。

はじめに

使うもの

前提条件

もし AWS のアカウントや Github のアカウントがないとか API トークンがないなら事前に取得しておく。 このあたりの情報は公式サイトはもちろんネット上にたくさんあるので困ることはないと思う。

やること

  • Pixela アカウントとグラフを作成
  • Lambda 関数の作成
    • トリガーを追加
    • 関数パッケージの作成
    • 関数パッケージのアップロード
    • 環境変数の設定
    • Lambda 関数の動作確認

Pixela アカウントとグラフを作成

Github のクローン数を Pixela に記録するために Pixela アカウントの作成と Pixela グラフを作成する。 公式ブログを見ながら作っていくと迷うことなく作れると思う。

blog.a-know.me

Lambda 関数の作成

関数の作成

Github のクローン数を取得して Pixela に記録する関数を作成していく。

AWS コンソール を表示して Lambda > 関数 > 関数の作成 ボタンを押す。

f:id:ebc_2in2crc:20190813155338p:plain:w600

一から作成 を選んで関数名を入力する。 ランタイムは Go 1.x を選んで実行ロールは 基本的な Lambda アクセス権限で新しいロールを作成 を選ぶ。

f:id:ebc_2in2crc:20190813155717p:plain:w600

トリガーを追加

作成した関数を定期的に動かすためにトリガーを設定していく。

Designer の トリガーを追加 を押してトリガーの設定画面を表示する。

f:id:ebc_2in2crc:20190813162049p:plain:w600

トリガーの設定画面は以下のように設定する。

  • トリガーの選択は CloudWatch Events を選択
  • ルールは 新規ルールの作成 を選択
  • ルール名は 0100-utc-everyday を入力
  • ルールタイプは スケジュール式 を選択
  • スケジュール式は cron(0 1 * * ? *) を入力

f:id:ebc_2in2crc:20190813162204p:plain:w600

トリガーの有効化 にチェックを付けて 追加 ボタンを押す。

f:id:ebc_2in2crc:20190813162826p:plain:w600

トリガーの設定はこれで完了。この設定をしておくと Lambda 関数が毎日1時 (UTC) に自動的に実行するようになる。

関数パッケージの作成

Lambda で動かす関数パッケージを作っていく。

以下のコードを main.go というファイル名で保存する。

package main

import (
    "context"
    "errors"
    "os"
    "strconv"
    "time"

    "github.com/aws/aws-lambda-go/lambda"
    "github.com/ebc-2in2crc/pixela-client-go"
    "github.com/google/go-github/github"
    "golang.org/x/oauth2"
)

type MyEvent struct {
    Time time.Time `json:"time"`
}

const timeFormat = "20060102"

var (
    githubToken = os.Getenv("GITHUB_TOKEN")
    githubUser  = os.Getenv("GITHUB_USER")
    githubRepo  = os.Getenv("GITHUB_REPO")

    pixelaToken = os.Getenv("PIXELA_USER_TOKEN")
    userName    = os.Getenv("PIXELA_USER_NAME")
    graphID     = os.Getenv("PIXELA_GRAPH_ID")
)

func main() {
    lambda.Start(HandleRequest)
}

func HandleRequest(event MyEvent) (string, error) {
    yesterday := event.Time.Truncate(time.Hour * 24).Add(-time.Hour * 24)
    cloneCount, err := retrieveCloneCount(yesterday)
    if err != nil {
        return "failed to retrieve clone count", err
    }

    if err := registerCloneCount(yesterday, cloneCount); err != nil {
        return "failed to register clone count", err
    }

    return "success", nil
}

func retrieveCloneCount(t time.Time) (int, error) {
    client := newGithubClient(githubToken)
    clones, resp, err := client.Repositories.ListTrafficClones(
        context.Background(),
        githubUser,
        githubRepo,
        &github.TrafficBreakdownOptions{Per: "day"},
    )
    if err != nil {
        return 0, err
    }
    _ = resp.Body.Close()

    d := t.Format(timeFormat)
    for _, c := range clones.Clones {
        if c.GetTimestamp().Format(timeFormat) == d {
            return c.GetCount(), nil
        }
    }
    return 0, nil
}

func newGithubClient(token string) *github.Client {
    oauthCli := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{
        AccessToken: token,
    }))
    return github.NewClient(oauthCli)
}

func registerCloneCount(date time.Time, cloneCount int) error {
    client := pixela.NewClient(userName, pixelaToken)
    pixel := client.Pixel(graphID)
    result, err := pixel.Create(date.Format(timeFormat), strconv.Itoa(cloneCount), "")
    if err != nil {
        return err
    }
    if result.IsSuccess == false {
        return errors.New(result.Message)
    }

    return nil
}

AWS Lambda にアップロードする zip ファイルを作成する。

$ go build -o hello main.go && zip hello.zip hello
  adding: hello (deflated 53%)

MacWindowsコンパイルするときは GOOS=linux を付けて Linux 向けにクロスコンパイルする。

$ go build GOOS=linux go build -o hello main.go && zip hello.zip hello
  adding: hello (deflated 53%)

main.go がやっていること

ざっくり書くと「プログラムの実行日付の前日の Github リポジトリのクローン数を取得」して「プログラム実行日付の前日の (PIxela の) Pixel に記録」している。 当日のクローン数を当日の Pixel に記録したいときは yesterday := event.Time.Truncate(time.Hour * 24).Add(-time.Hour * 24) のあたりを適当にいじるとよい。

関数パッケージのアップロード

作成した zip ファイルを AWS にアップロードしていく。

コードエントリタイプは .zip ファイルをアップロード を選択して アップロード ボタンを押してさきほど作成した zip ファイルを AWS にアップロードする。

f:id:ebc_2in2crc:20190813163945p:plain:w600

環境変数の設定

関数パッケージが使う環境変数アップロード ボタンの下の 環境変数 に設定していく。

API トークンなど重要な情報を含んでいるので実際は暗号化するほうがよい。

環境変数 設定する値
GITHUB_TOKEN GithubAPI トーク
GITHUB_USER Github のユーザー名
GITHUB_REPO クローン数の草を生やしたいリポジトリ
PIXELA_USER_TOKEN Pixela の API トーク
PIXELA_USER_NAME Pixela のユーザー名
PIXELA_GRAPH_ID クローン数の草を生やす先のグラフの名前

これで Lambda 関数の作成は完了。 続いて Lambda 関数がちゃんと動くか確認していく。

Lambda 関数の動作確認

関数パッケージと環境変数が正しく設定されていて Lambda 関数がちゃんと動くかどうかを確認していく。

画面の上のほうにある テストイベントの設定 を押して テストイベントの設定 画面を表示する。

f:id:ebc_2in2crc:20190813164827p:plain:w600

テストイベントの設定 画面は以下のように設定する。

  • 新しいテストイベントの作成 を選択
  • イベントテンプレートは Hello World を選択
  • イベント名は test を入力

イベントの JSON は以下を設定する。 2019-08-13 のところはテストをしたい日付、たとえば Github リポジトリがクローンされたことが分かっている日、あるいは Github リポジトリがクローンされなかったことが分かっている日などを入力する。

{
  "time": "2019-08-13T01:00:00Z"
}

入力し終わったら 保存 ボタンを押してテストイベントを保存する。

次は作成したテストイベントを使って Lambda 関数の実際の動きを確認していく。

画面の上のほうにある テスト ボタンを押してテストイベント test を使って Lambda 関数を動かす。

f:id:ebc_2in2crc:20190813170555p:plain:w600

実行結果: 成功 が表示されれば Lambda 関数は問題なく動いている。

f:id:ebc_2in2crc:20190813170815p:plain:w600

以上。

参考ページ

blog.a-know.me

developer.github.com

EC2 インスタンスのメタデータを取得する ec2-metadatafs コマンドが便利すぎた。

AWS の EC2 インスタンスメタデータを取得する ec2-metadatafs を使ってみたら便利すぎたのでメモ。

ec2-metadatafs ってなに?

ec2-metadatafs は AWS の EC2 インスタンスメタデータを取得できるコマンド。

ec2-metadatafs の特徴は EC2 インスタンスメタデータファイルシステム上にマウントすることによって ls とか cat とか grep とかの CLI ツールと一緒に使うことが容易になること。 また、メタデータファイルシステム上に配置するのでシェルの補完機能を最大限に活用することができるのがとても便利。

さっそく使ってみる。

$ mkdir metadata

$ ec2-metadatafs --tags metadata/
2019/08/12 05:38:35 [INFO] forked child with PID 3459
2019/08/12 05:38:35 [INFO] child process successfully mounted

$ tree metadata/
metadata/
├── dynamic
│   └── instance-identity
│       ├── document
│       ├── pkcs7
│       ├── rsa2048
│       └── signature
├── meta-data
│   ├── ami-id
│   ├── ami-launch-index
│   ├── ami-manifest-path
│   ├── block-device-mapping
│   │   ├── ami
│   │   └── root
│   ├── events
│   ├── hostname
│   ├── iam
│   │   ├── info
│   │   └── security-credentials
│   │       └── ec2-describetag-role
│   ├── identity-credentials
│   ├── instance-action
│   ├── instance-id
│   ├── instance-type
│   ├── local-hostname
│   ├── local-ipv4
│   ├── mac
│   ├── metrics
│   ├── network
│   │   └── interfaces
│   │       └── macs
│   │           └── 06:e5:0c:b8:f7:3e
│   │               ├── device-number
│   │               ├── interface-id
│   │               ├── ipv4-associations
│   │               ├── local-hostname
│   │               ├── local-ipv4s
│   │               ├── mac
│   │               ├── owner-id
│   │               ├── public-hostname
│   │               ├── public-ipv4s
│   │               ├── security-group-ids
│   │               ├── security-groups
│   │               ├── subnet-id
│   │               ├── subnet-ipv4-cidr-block
│   │               ├── vpc-id
│   │               ├── vpc-ipv4-cidr-block
│   │               └── vpc-ipv4-cidr-blocks
│   ├── placement
│   │   └── availability-zone
│   ├── profile
│   ├── public-hostname
│   ├── public-ipv4
│   ├── public-keys
│   │   └── 0
│   │       └── openssh-key
│   ├── reservation-id
│   ├── security-groups
│   └── services
│       ├── domain
│       │   └── amazonaws.com
│       └── partition
└── tags
    ├── Tag1
    └── Tag2

備忘録までにそれぞれのコマンドがやっていることを軽く書いておく。 まず metadata ディレクトリを作っているところ。

$ mkdir metadata

EC2 インスタンスメタデータをマウントするディレクトリを作っている。 メタデータをマウントする場所はどこでもいいと思うけどユーザーが複数いてそれぞれのユーザーがメタデータを取得するのなら、メタデータはどのユーザーからも見える場所にマウントするとよいと思う。

次は ec2-metadatafs コマンドを実行しているところ。

$ ec2-metadatafs --tags metadata/
2019/08/12 05:38:35 [INFO] forked child with PID 3459
2019/08/12 05:38:35 [INFO] child process successfully mounted

ec2-metadatafs コマンドを使って EC2 インスタンスメタデータをさきほど作った metadata にマウントする。それだけ。

あと EC2 インスタンスのタグを取得するために --tags オプションを指定しているがタグは取得しないのなら指定する必要はない。

次は EC2 インスタンスメタデータをマウントした metadata ディレクトリを tree してみたところ。 EC2 インスタンスメタデータファイルシステム上にマウントされているのがわかる。

$ tree metadata/
metadata/
├── dynamic
│   └── instance-identity
│       ├── document
│       ├── pkcs7
│       ├── rsa2048
│       └── signature
└── meta-data
    ├── ami-id

# 長いので省略

ec2-metadatafs を使ってみる

EC2 インスタンスメタデータファイルシステム上にマウントされる、ただそれだけのことなのだが実際に使ってみると想像以上に便利なのでいくつか使用例を書いておく。

試しにインスタンスタイプを取得してみる。

$ cat metadata/meta-data/instance-type
t2.micro

簡単。

もう一つ試しに EC2 インスタンスが動いているアベイラビリティゾーンを取得してみる。

$ cat metadata/meta-data/placement/availability-zone
ap-northeast-1a

すごく簡単。

ec2-metadatafs が便利なところはシェルの補完機能が働くので「あのメタデータどこだっけ?」ってときも雰囲気で入力して補完機能に任せる、みたいな使い方ができること。これが本当に便利。

補完機能を使うのすら面倒なときは find コマンドとかを使えば一切考える必要もない。

$$ find metadata/ -name "*zone*"
metadata/meta-data/placement/availability-zone

繰り返すが便利。

あとタグを取得できるのが地味だけどかなり便利。

$ ls metadata/tags/
Tag1  Tag2

$ cat metadata/tags/Tag1
Tag1Value

EC2 インスタンスメタデータを取得する方法はいろいろある

EC2 インスタンスメタデータを取得する方法は ec2-metadatafs の他にもあるのでメモしておく。 それぞれがメリット・デメリットがあるので、状況によって使い分けるのがいいと思う。

http://169.254.169.254/latest/meta-data/

http://169.254.169.254/latest/meta-data/ にアクセスすると EC2 インスタンスメタデータを取得できる。

$ curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hostname
identity-credentials/
instance-action
instance-id
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/

インスタンスタイプを取得してみる。

$ curl http://169.254.169.254/latest/meta-data/instance-type
t2.micro

難しいことは一切ないが URL を打ち込むのが面倒くさいし URL を打ち間違えたときに 404 が返ってくるのがイラつく。コピペしたくなってくる。「あれ、インスタンスタイプの URL はなんだったっけ?」というときは http://169.254.169.254/latest/meta-data/ にアクセスして結果を眺めて「あ、instance-type だったな」とやる以外にないのが不便。

あと、どうやってもタグを取得できないのが地味に結構不便。

ただ、http://169.254.169.254/latest/meta-data/ の素晴らしいところは EC2 デフォルトの状態でも使えるということ。 あと、スクリプトの中からメタデータを取得したいときみたいなときは事前に知りたいメタデータの URL とかを調べているだろうし動作も確認しているだろうから、URL を打ち込むのが面倒くさいとか URL を打ち間違えたとかはないはず。

ec2-metadata コマンド

ec2-metadata は EC2 インスタンスメタデータを取得するコマンド。

$ ec2-metadata --help
ec2-metadata v0.1.2
Use to retrieve EC2 instance metadata from within a running EC2 instance.
e.g. to retrieve instance id: ec2-metadata -i
         to retrieve ami id: ec2-metadata -a
         to get help: ec2-metadata --help
For more information on Amazon EC2 instance meta-data, refer to the documentation at
http://docs.amazonwebservices.com/AWSEC2/2008-05-05/DeveloperGuide/AESDG-chapter-instancedata.html

Usage: ec2-metadata <option>
Options:
--all                     Show all metadata information for this host (also default).
-a/--ami-id               The AMI ID used to launch this instance
-l/--ami-launch-index     The index of this instance in the reservation (per AMI).
-m/--ami-manifest-path    The manifest path of the AMI with which the instance was launched.
-n/--ancestor-ami-ids     The AMI IDs of any instances that were rebundled to create this AMI.
-b/--block-device-mapping Defines native device names to use when exposing virtual devices.
-i/--instance-id          The ID of this instance
-t/--instance-type        The type of instance to launch. For more information, see Instance Types.
-h/--local-hostname       The local hostname of the instance.
-o/--local-ipv4           Public IP address if launched with direct addressing; private IP address if launched with public addressing.
-k/--kernel-id            The ID of the kernel launched with this instance, if applicable.
-z/--availability-zone    The availability zone in which the instance launched. Same as placement
-c/--product-codes        Product codes associated with this instance.
-p/--public-hostname      The public hostname of the instance.
-v/--public-ipv4          NATted public IP Address
-u/--public-keys          Public keys. Only available if supplied at instance launch time
-r/--ramdisk-id           The ID of the RAM disk launched with this instance, if applicable.
-e/--reservation-id       ID of the reservation.
-s/--security-groups      Names of the security groups the instance is launched in. Only available if supplied at instance launch time
-d/--user-data            User-supplied data.Only available if supplied at instance launch time.

インスタンスタイプを取得してみる。

$ ec2-metadata --instance-type
instance-type: t2.micro

<メタデータの名前>: <メタデータの値> みたいな感じで出力されるので、メタデータの値たとえばインスタンスタイプの t2.micro の部分だけを取得するなら cut とかで切り出す感じ。ちょっと面倒くさい。

$ ec2-metadata --instance-type | cut -d ' ' -f 2
t2.micro

URL を打ち込む必要がないしショートオプションを使うとタイプ量はさらに少なくなるのが便利。 あと AMI が Amazon Linux ならデフォルトで使えるメリットが大きい。

ただ、メタデータの値を cut とかで切り出すのが面倒くさい。 あと、どうやってもタグを取得できないのが地味に結構不便。

ec2-metadatafs のインストール

ec2-metadatafs コマンドが便利な点と使い方の雰囲気は書いたのでインストール方法も書いておく。

といっても、curlhttps://github.com/jszwedko/ec2-metadatafs/releases/download/<バージョン>/linux_amd64 からダウンロードして実行パーミッションを付けてパスが通ったところに置くだけ。

$ curl -sL https://github.com/jszwedko/ec2-metadatafs/releases/download/1.0.0/linux_amd64 > ec2-metadatafs
$ chmod +x ec2-metadatafs
$ sudo mv ec2-metadatafs /usr/bin/

ec2-metadatafs は EC2 メタデータファイルシステム上にマウントするために FUSE に依存しているので FUSE をインストールしておく。

 $ sudo yum install -y fuse

あとタグを取得するときは IAM ロールが必要になるのでつけておく。

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

IAM ロールを設定したくないときは ec2-metadatafs コマンドを使うときに --aws-access-key-id オプションとか --aws-secret-access-key とかを指定してあげてもいい。

ec2-metadatafs を使い終わったら

ec2-metadatafs を使い終わったらというか「あんまり便利じゃないしやっぱ使わなくていいや」というお気持ちになったときは EC2 インスタンスメタデータをアンマウントしておく。

$ fusermount -u metadata

これだけ。

AWS CLI で意図していないプロファイルを使ってしまうのを防止するコマンドを書いたメモ。

AWS CLI で意図していないプロファイルを使ってしまうのを防止するコマンドを書いたメモ。

動機

AWS CLI はプロファイルを --profile オプションや AWS_DEFAULT_PROFILE 環境変数で指定できるようになっていて AWS アカウントや IAM ユーザーが複数あってもあまり手間なくいろいろな操作を行うことができる反面、使うプロファイルを間違うといとも簡単に悲惨なことが起こり得る。

日常の作業の多くは AWS アカウント向け / 操作対象の環境向けのスクリプトを書くなりして操作の入り口から分けてしまうなどの対策を取ればかなりの事故は減らせるが、ちょっとしたお試しとか突発的な作業をするときはどうしても生の aws コマンドを触ってしまいがちでどうしても事故が発生しやすくなってしまう。

そこで、AWS CLI を使うときに Githubリポジトリを消すときの Please type in the name of the repository to confirm. みたいに処理対象を入力させるシステムがあったら事故が減らせるのでは? と思った。

↓こういうやつ。

f:id:ebc_2in2crc:20190728165659p:plain:w400
Githubリポジトリを消すときのアレ

ラップコマンドの実行イメージ

実行イメージはこんな感じ。

AWS CLI のラップコマンド staging-hoge-aws を実行すると対象のアプリケーションと対象の環境を聞いてくる。この実行イメージは hoge が対象のアプリケーションで staging が環境。 staging-hoge-aws コマンドはコマンド内にハードコーディングされたアプリケーションおよび環境とユーザーが入力するアプリケーションおよび環境が 一致するときだけ実際の aws コマンドを実行する。この実行イメージは aws s3 ls が実際のコマンド。

$ staging-hoge-aws s3 ls
Enter target application name: hoge
Enter target environment name: staging

2019-07-27 11:13:21 xxx-bucket
2019-07-27 11:13:21 yyy-bucket

staging-hoge-aws コマンドはユーザーが入力するアプリケーションか環境が間違っているとメッセージを表示して実際のコマンドは実行しない。

$ staging-hoge-aws s3 ls
Enter target application name: fuga
Invalid application name: fuga

このラップコマンドはアプリケーションと環境の組み合わせ分を作っておく。 たとえば hoge アプリケーションは本番環境とステージング環境があるなら production-hoge-awsstaging-hoge-aws を作っておく。

本番環境用のラップコマンドはアプリケーションと環境の確認に加えて「本番環境をいじろうとしてるけど本当にいいの?」的なことを聞いてきてユーザーが yes を入力したときだけ実際の aws コマンドを実行する。ユーザーが yes 以外を入力したときは実際の aws コマンドは実行しない。

$ production-hoge-aws s3 ls
Enter target application name: hoge
Enter target environment name: production
You will make changes to the production environment!!!!!
Are you sure you want to continue (yes/no)?: yes

# ユーザーが yes を入力したときだけ実際の aws コマンドが実行される
2019-07-27 11:13:21 xxx-bucket
2019-07-27 11:13:21 yyy-bucket

ラップコマンドの中身

staging-hoge-aws はこんな感じになっている。 単純に対象のアプリケーションと環境がハードコーディングしてあってユーザーの入力と一致するときに実際の aws コマンドを実行するだけ。

新しいアプリケーションあるいは環境用のコマンドをつくるときは TARGET_APPTARGET_ENV だけ書き換える感じ。

#!/bin/bash

TARGET_APP=hoge
TARGET_ENV=staging
PROFILE=${TARGET_ENV}-${TARGET_APP}

echo -n 'Enter target application name: '
read APP_NAME
if [ "${APP_NAME}" != "${TARGET_APP}" ]; then
    echo "Invalid application name: ${APP_NAME}"
    exit 1
fi

echo -n 'Enter target environment name: '
read ENV_NAME
if [ "${ENV_NAME}" != "${TARGET_ENV}" ]; then
    echo "Invalid environment name: ${ENV_NAME}"
    exit 1
fi

if [ "${ENV_NAME}" == "production" ]; then
    echo "You will make changes to the production environment!!!!!"
    echo -n 'Are you sure you want to continue (yes/no)?: '
    read CONFIRM
    if [ "${CONFIRM}" != "yes" ]; then
        exit 1
    fi
fi
echo ''

aws --profile $PROFILE "$@"

プロファイル

config ファイル

config ファイルはこんな感じにしておく。

[profile staging-hoge]
output = json
region = ap-northeast-1

[profile production-hoge]
output = json
region = ap-northeast-1

credentials ファイル

credentials ファイルはこんな感じにしておく。

[staging-hoge]
aws_access_key_id = <ステージング環境のアクセスキー>
aws_secret_access_key = <ステージング環境のシークレットキー>

[production-hoge]
aws_access_key_id = <本番環境のアクセスキー>
aws_secret_access_key = <本番環境のシークレットキー>

参考にしたサイト

togetter.com

本番環境とステージング環境を取り違える事故を減らしたくて Twitter で聞いてみたら本職のインフラエンジニアから DevOps やってる人まで珠玉の知見を教えてもらいまくったのを参考にした。感謝しかない……っ!!