Blogs

    in  Terraform

    Terraformのリファクタリング: リモートのアカウントID参照

    Terraformのリファクタリング楽しいですね。ついついいろんなハードコードを変数化するのに熱中してしまいます。

    AWSをTerraformで構築・運用するときAWSのアカウントIDが必要になることがあります。 特にVPC Peering、Private Link, SNS, S3のBucket Policyなど、クロスアカウントで利用できるリソースなどは別アカウントのIDが欲しくなる事があります。 一つならまだしも2つ以上アカウントIDと連携したりすると混乱するので、ここにハードコードしたIDではなくわかりやすい変数名、しかもリモートステートで別アカウントのIDが参照できれば良いなと思いました。

    自アカウントであれば、aws_caller_identityを利用すれば簡単に取得することができます。

    1
    
    data "aws_caller_identity" "self" { }
    

    どこかに上記のような記述をすると、下記のような形でアカウントIDを参照できます。

    1
    
    "${data.aws_caller_identity.self.account_id}"
    

    これにリモートステートの仕組みをプラスすることで、別アカウントのIDを変数化できました。 参照される側のアカウントをA, 参照する側のアカウントをBとしています。

    参照される側のアカウントの設定

    まずは参照されるアカウントAの設定です。 別アカウントBに変数を公開するにはoutputとして登録する必要があります。 今回は変数名をaccount_idとして、AWSのアカウントIDを公開しています。

    1
    2
    3
    4
    5
    
    data "aws_caller_identity" "self" {}
    
    output "account_id" {
      value = "${data.aws_caller_identity.self.account_id}"
    }
    

    また、S3のバケットポリシーを編集して、TerraformのStateをアカウントBが閲覧できるように設定をする必要があります。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "ExampleStatement1",
                "Effect": "Allow",
                "Principal": {
                    "AWS": [
                        "arn:aws:iam::[アカウントBのID]:root",
                    ]
                },
                "Action": [
                    "s3:GetBucketLocation",
                    "s3:ListBucket",
                    "s3:GetObject"
                ],
                "Resource": [
                    "arn:aws:s3:::[アカウントAのStateが入っているBucket]",
                    "arn:aws:s3:::[アカウントAのStateが入っているBucket]"/*"
                ]
            }
        ]
    }
    

    参照される側のアカウントの設定

    参照する側のアカウントBでは参照するアカウントAのStateを参照する設定を追加します。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    data "terraform_remote_state" "account_a" {
      backend = "s3"
    
      config {
        bucket = "[アカウントAのStateが入っているBucket]"
        key    = "state"
        region = "ap-northeast-1"
      }
    }
    

    例えばアカウントAとBのVPC Peeringの設定とかで利用できます。

    1
    2
    3
    4
    5
    6
    
    
    resource "aws_vpc_peering_connection" "acount_a_b" {
      peer_owner_id = "${data.terraform_remote_state.account_a.account_id}"
      peer_vpc_id   = "[アカウントAのVPC ID]"
      vpc_id        = "[アカウントBのVPC ID]"
    }
    

    アカウントAのVPC IDもリモートステートで取得する手もありますが、今回は割愛しました。

    まとめ

    Terraformを書いていていろんなアカウントと連携する設定を書くときに、アカウントID部分が数字の羅列だと混乱するので変数化してみました。 クロスアカウントの機能は充実していますし機能拡張もされ続けています。 何かと使い所はあるのではないでしょうか。


    in  Packer

    Packerを初めて使ってハマったこと

    最近はDockerの環境を運用することが多いので、すっかりEC2に対してAnsibleで環境を構築したりメンテナンスしたりすることが減りました。 それでもAWS上のECSをEC2ベースで運用していて、EC2に監視用のエージェントなどをインストールしているケースなどもあると思います。 例えば自分の場合だとPrometheusの監視モジュール、node exporterを各EC2にインストールしています。

    今後もECS OptimizedなEC2はどんどん更新されていくので、これを機にPackerを導入してベースイメージの作成をすることにしました。 ちなみにPackerを導入しようと思ったのは先日発表されたDockerの脆弱性対応のためEC2をアップデートしないといけなかったことがきっかけです。

    というわけでPackerは初めて触ったわけですが、ほとんどはまることもなくAnsibleをプロビジョナーとする構成を作ることができました。 それでも何点かつまずいた点があったので、残しておきます。

    環境は下記の通りです。

    • Mac OS Mojave 10.14.3
    • Packer 1.3.4
    • Ansible 2.6.5

    Packerのインストール

    Macを前提とします。brewで簡単にインストールできます。

    $ brew install packer
    

    最終的にbuild.jsonは下記のようになりました。 ECS用のEC2の最新版を取ってきてansibleでプロビジョンしています。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    {
        "provisioners": [
    	{
    	    "type": "ansible",
    	    "playbook_file": "./playbook.yml"
    	}
        ],
        "builders": [{
    	"type": "amazon-ebs",
    	"region": "ap-northeast-1",
    	"source_ami_filter": {
    	    "filters": {
    		"virtualization-type": "hvm",
    		"name": "amzn-ami-*-amazon-ecs-optimized",
    		"root-device-type": "ebs"
    	    },
    	    "owners": ["591542846629"],
    	    "most_recent": true
    	},
    	"instance_type": "t3.nano",
    	"ssh_username": "ec2-user",
    	"ami_name": "amzn-ami-amazon-ecs-optimized-20190225",
    	"subnet_id": "subnet-xxxxxxxxxx",
    	"associate_public_ip_address": true
        }]
    }
    

    つまずきを何点か

    default subnetが存在しない場合

    今回、Ansibleでプロビジョンするにあたり、インターネット上のモジュールをダウンロードする必要がありました。 インターネットにアクセスできない状態だとAnsibleが途中で落ちます。

    PackerはPublic IPを取得できるdefault subnet上でAMIを起動しようとします。 default subnetが存在しない環境だとSubnetを指定してあげる必要があります。 そこで上記のようにsubnet_id でsubnetを指定しています。

    PublicIPが自動割り当てされないSubnetの場合

    Subnetの項目のうち、Auto-assign public IPv4 addressがNoになっている場合、Public IPが自動で割り当てられません。 “associate_public_ip_address”: true としてPublic IPが割り当てられるようにしましょう。

    Ansibleを含めたディレクトリ構成

    ansibleのPlaybookをどこに設置すれば良いか、一瞬悩みました、 なんのことはない、build.jsonと同じ階層に置けば良いだけでした。 ディレクトリ構成はこんな感じになりました。

    ├── build.json
    ├── playbook.yml
    └── roles
        └── exporter
            ├── handlers
            │   └── main.yml
            ├── tasks
            │   └── main.yml
            ├── templates
            │   ├── node_exporter.default
            │   └── node_exporter.init.d.amzn.j2
            └── vars
                └── main.yml
    

    まとめ

    Packerはやれることも単純なこともあり、そこまで複雑でハマることもないツールです。 何点かつまずきポイントを載せましたが、エラーメッセージを見ればわかるっていうものでした。 単純ですが、OSのバージョンアップに追従したベースイメージを作成する際に非常に重宝しています。


    in  MySQL

    MySQLのibdataから個別のテーブルデータをリストアする

    バックアップは取っていてもリストアできないと宝の持ち腐れですね。

    ibdataのコールドバックアップは取っていて、サクッと一部のテーブルのデータのみリストアする方法です。 稼働中のMySQLを止める必要がないので、一部のテーブルだけ復旧したい場合や、とりあえず昔のテーブルの状況を見たい場合などに利用可能です。 データベース全体のリストアではないので、リストアの時間を短縮したいときに使えるかと思います。

    やり方としては公式のドキュメントに書いてある通りなのですが、もうちょっと細かくやり方を見ていきます。 innodb_file_per_tableがONになっていて、テーブル毎にibdataが作成されていることが前提になります。

    大まかな手順は下記のようになります。

    1. 復旧したいデータベース・テーブルがない場合はあらかじめ作成しておく
    2. 該当テーブルへの変更をLOCKする
    3. テーブルスペースを削除(ibdファイルを削除)
    4. 復旧したいibdファイルをコピーする
    5. テーブルスペースをインポート
    6. Lockを解除

    下記のバージョンで検証しました。

    • MySQL: 5.6.42
    • CentOS: 7.5

    準備

    テストのためのデータベースとテーブル・テストデータを作成します。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    mysql> CREATE DATABASE `test`  CHARACTER SET utf8mb4;
    mysql> use test;
    mysql> CREATE TABLE `test_table` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(255),
      `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    mysql> insert into test_table (name , updated_at, created_at) value ('hoge', now(), now());
    mysql> insert into test_table (name , updated_at, created_at) value ('fuga', now(), now());
    
    mysql> select * from test_table;
    +----+------+---------------------+---------------------+
    | id | name | updated_at          | created_at          |
    +----+------+---------------------+---------------------+
    |  1 | hoge | 2018-12-01 11:11:37 | 2018-12-01 11:11:37 |
    |  2 | fuga | 2018-12-01 11:11:57 | 2018-12-01 11:11:57 |
    +----+------+---------------------+---------------------+
    

    MySQLを止めて、/var/lib/mysql/test/test_table.ibd をバックアップします。

    1
    2
    
    
    $ sudo systemctl stop mysqld.service
    

    リストア

    1. リストア先のデータベース・テーブルの準備

    復旧したいデータベース・テーブルがない場合はあらかじめ作成します。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    mysql> CREATE DATABASE `test`  CHARACTER SET utf8mb4;
    mysql> use test;
    mysql> CREATE TABLE `test_table` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(255),
      `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    もちろんこの段階ではデータは存在しません。

    1
    2
    
    mysql> select * from test_table;
    Empty set (0.00 sec)
    

    2. 該当テーブルへの変更をLOCK

    該当テーブルへの変更をLOCKします。

    1
    2
    
    mysql> LOCK TABLES test_table WRITE;
    Query OK, 0 rows affected (0.00 sec)
    

    3. テーブルスペースの削除

    テーブルスペースを削除(ibdファイルを削除)します。

    1
    2
    
    mysql> ALTER TABLE test_table DISCARD TABLESPACE;
    Query OK, 0 rows affected (0.00 sec)
    

    4. 復旧するファイルの移動

    復旧したいibdファイルをコピーします。

    権限も正しくmysqlユーザーで読み書きできるように設定しましょう。

    1
    2
    3
    4
    5
    6
    
    $ sudo ls -la /var/lib/mysql/test/
    drwx------. 2 mysql mysql  4096 Dec  1 11:44 .
    drwxr-xr-x. 6 mysql mysql  4096 Dec  1 11:42 ..
    -rw-rw----. 1 mysql mysql    67 Dec  1 11:42 db.opt
    -rw-rw----. 1 mysql mysql  8670 Dec  1 11:42 test_table.frm
    -rw-r-----. 1 mysql mysql 98304 Dec  1 11:45 test_table.ibd
    

    5. テーブルスペースをインポート

    テーブルスペースをインポートします。

    1
    2
    
    mysql> ALTER TABLE test_table IMPORT TABLESPACE;
    Query OK, 0 rows affected, 1 warning (0.02 sec)
    

    6. Lockを解除

    ロックを解除します。 また、データが存在することが確認できます。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    mysql> UNLOCK TABLES;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from test_table;
    +----+------+---------------------+---------------------+
    | id | name | updated_at          | created_at          |
    +----+------+---------------------+---------------------+
    |  1 | hoge | 2018-12-01 11:11:37 | 2018-12-01 11:11:37 |
    |  2 | fuga | 2018-12-01 11:11:57 | 2018-12-01 11:11:57 |
    +----+------+---------------------+---------------------+
    

    まとめ

    AWSのRDSなど、マネージドのデータベース環境を利用することが最近は多くなりました。 RDSならリストアもマネージコンソールでぽちぽちやれば簡単に切り戻せて便利ですね。

    とはいえ、まだMySQLを様々な理由で自分達で運用している環境もあるかと思います。 コールドバックアップから手軽にテーブル単位でリストアできるのは便利ですね。


    in  Docker, Book

    「Docker/Kubernetes実践コンテナ開発入門」を読んだ

    OreillyのDocker本に続いて読んだのが、現時点では比較的最近出版された本書でした。

    出来るだけ新しいDocker本で最近の情報で知識をアップデートしたかったのと、Kubernetesの説明が充実してそうというのが本書を手に取った動機です。

    Dockerに正式採用されたKubernetesと、Swarmの違いなんかも体験できればなと思いました。

    内容

    Dockerの基本的な使い方から入るところは、先に読んだOreilly本と同じでしたが、目論見通りOreilly本の時点から色々変化があった事を知ることができました。 大きな変化はKubernetesの正式採用などがありました。 小さな変化ははDockerコマンドについてのコラムにあった docker container というコマンド形式で、 以前は省略していた “container"の指定が現在は推奨されつつある等です。 dockerコマンドではpruneオプションなんかも知ることができました。

    構成としてはDockerの基本を学び、オーケストレーションを体験し、実際に運用する上での知識をつけていく流れなのですが、 サンプルで作成したToDoアプリを題材に、Swarmを経て Kubernetesへ載せていく流れでオーケストレーションについての知識が深められ非常に分かりやすかったです。 また、本番で使っているからこその著者のDockerについての考え方や知見が随所に散りばめられており、 後半の章、特に9章での軽量なDockerイメージの作り方はDockerの内部構造を意識する上でも非常にためになりました。

    特に面白かった所

    docker-composeで作成したToDoアプリが、最終的にKubernetesに載る所が、1つのクライマックスかと思います。 本書ではGCPでKubernetesを利用していますが、私はEKSでサンプルを試していきました。 ロードバランサの取り回しなど、細かい点は考慮が必要ですが、どちらのプラットフォームでも動作させれる点が、Docker, Kubernetesの強みであると実感できました。

    また、コラムが非常に充実していて、日々運用する上で何に困って、どういうツールで解決していったかを知ることができ、運用の雰囲気を知る上で有用でした。

    まとめ

    Dockerの進化を感じつつも、実践という名を冠しているだけあって、著者が本番でDockerを運用し、格闘して得た知識が詰まっています。 私自身はKubernetesに移行するかは検討段階で、まだまだやる事が山積みですが、本書を片手に現在のDockerの運用を改善しつつ、来るべきオーケストレーションに備えたいと思います。


    in  Docker, Book

    Oreillyの「Docker」を読んだ

    Dockerのチュートリアルを軽く触った事しかなかったのですが、本番でDockerを運用しているシステムを触ることになったので手に取りました。 しかしDockerは進化のスピードが早くてどの本を買おうか非常に迷いましたね。

    この本は2016年の出版ですが、パッと見た所そこまで難しすぎず、手を動かしながらDockerを体験できそうだったのと、 迷ったらオライリーでしょっていう安易な考え方で選びました。

    2年の月日はこの分野では大きな差がありそうですが、 特に進化が激しそうなオーケストレーション周りはこの本とは別に直近に出版されたものも読んで、 Dockerの進化を疑似体験しつつ知識をアップデートしていこうという目論見です。

    内容

    DockerでHello Worldする所から始まり、簡単なWebアプリケーションの構築を通して、Dockerの基本的な操作を学べました。 これまでVMを触る事が多かった身としては、VMとの違いや利点も知りたい所でしたが、その点もしっかり押さえられている印象でした。 ただ単にVMに比べて起動が早いというわかりやすい利点の他に、Docer Hubを介したエコシステムが開発効率を上げてくれることを体感できます。 やはりその可搬性からデプロイまでも含めた流れが、VMとの大きな違いですね。 例えばサンプルにも出てきますが、既存の構成にキャッシュを組み込む際に、redis用のコンテナをサクッとpullしてリンクしてあげるような基本的な部分から Docker HubでのAuto Buildやステージング・本番で同一コンテナを利用する方法なども記載があり、 Dockerの思想を学びながら実際の現場で使えるようなイメージが湧きました。

    要素としては、dockerコマンドやDockerfile, docker-compose, docker-machine等の基本的なコマンドや設定ファイルについて、 丁寧なサンプルが載っているので、写経していくには良かったです。 Dockerを利用したWebアプリケーションの構築やそのエンハンスを手を動かして一通り体験できるので、分かった気にさせてくれます。

    特に面白かった所

    これまで存在を知っていたconsul等のサービスディスカバリに触れられたことは、予想外の利点でした。 AWSのRDS等の各サービスを利用している際は内部DNSが裏で運用されて、利用する際はDNS名を意識すればよいだけですが、 Dockerで各コンテナ間で通信を行う際は、DNSでやるかどうかも含めて通信先を管理する必要があります。 そこでサービスディスカバリが必要になるわけです。 このDocker間の通信やサービスディスカバリがDockerで重要な位置を占めている点が知れた事が大きな収穫でした。

    サービスディスカバリとコンテナの登録・削除を自動的に連携させるところまで細かく触れられていませんが、 手動でconsulやetcdのAPIを叩く事でコンテナの登録・削除を行う事でサービスディスカバリの働きを知る事ができました。

    またホスト間をまたがったネットワークを構築するとなると、linkほど簡単に行かない点も興味深い点でした。 これまでAWSでEC2+RDSのようなオーソドックスな構成を運用する事が多かったため、 コンテナならではの考慮点がしっかり記載してある点は非常にためになりました。

    まとめ

    コンテナの良い所と苦労する点が非常にまとまってる本でしたが、 サンプルの通りに動かない点なども少々ありました。

    それだけこの界隈の進化のスピードが早いという事でしょう。 やはりこの本だけでDockerを学ぶのではなく、直近に出た本でも知識のアップデートをしておく必要があると感じました。 特に、ネットワークやオーケストレーション周りですね。

    とはいえ、Dockerをあまり触ったことのない方が一通り体験するには良い本だと思います。