kikukawa's diary

都内で活動するシステムエンジニアが書いてます。 興味を持った技術やハマったポイント、自分用メモをつけてます。 最近はweb中心

for_eachで作成したリソースのstateを指定する

for_eachで作成したリソースは、どのようにstateを指定するか戸惑ったのでメモです。

リソースの種類.リソース名["for_eachのkey"] で指定するみたいです。

もともとそれぞれ別リソースを作成していた場合にfor_eachで書き直した場合を考えてみます。

before

resource "aws_ssm_parameter" "foo" {
  name  = /foo
  value = "example-foo"
  type  = "String"
}
resource "aws_ssm_parameter" "bar" {
  name  = /bar
  value = "example-bar"
  type  = "String"
}

after

locals {
  params = [
    "foo",
    "bar",
  ]
}
resource "aws_ssm_parameter" "list" {
  for_each = toset(local.params) //listをtosetでfor_eachで使えるようにします。keyもvalueも同じ値が入ります。
  name  = "/${each.key}"
  value = "example-${each.key}"
  type  = "String"
}

state mv

こんな感じで指定します。

$ terraform state mv aws_ssm_parameter.foo 'aws_ssm_parameter.list["foo"]'
$ terraform state mv aws_ssm_parameter.bar 'aws_ssm_parameter.list["bar"]'

参考

https://www.terraform.io/docs/commands/import.html

DockerでSTFPを検証用に立てる

開発時の検証用として立ててみました。

docker image

https://github.com/atmoz/sftp

使い方

$ docker run -p 2222:22 -d atmoz/sftp foo:pass:::upload

fooというユーザーがpassというパスワードで生成されます。 /home/foo/upload が作成されて、そこに書き込みができます。

$ mkdir ~/upload
$ docker run \
  -p 2222:22 \
  -v ~/upload:/home/foo/upload \
  -d atmoz/sftp foo:pass:::upload

upload先をmountしたい場合は -v を指定します。

秘密鍵を使った認証の場合は、公開鍵をマウントし、コマンドでパスではなくuidをふってあげます。

$ mkdir ~/upload
$ mkdir ~/ssh
$ ssh-keygen -t rsa -b 4096 -N "" -C "" -m pem -f ~/ssh/sftp_rsa
$ docker run \
  -p 2222:22 \
  -v ~/upload:/home/foo/upload \
  -v ~/ssh/sftp_rsa.pub:/home/foo/.ssh/keys/sftp_rsa.pub:ro \
  -d atmoz/sftp foo::1001

接続確認

$ sftp -oPort="2222" foo@localhost
$ sftp -oPort="2222" -oIdentityFile=~/ssh/sftp_rsa foo@localhost

SFTPコマンドの使い方はこちら

kkkw.hatenablog.jp

sshの鍵作成方法はこちら

kkkw.hatenablog.jp

embulk-input-bigqueryでエラー

embulk-input-bigquery を使用したときのエラーをメモです。
エラーメッセージが全然優しくなくて、プラグインのソース見て推測するしかないので辛いです。

実行環境

embulk v0.9.3
embulk-input-bigquery v0.0.9

locationの指定漏れ

embulk-input-bigqueryには、datasetのlocationを指定する location があるのですが、これを指定しないとエラーになります。
datasetのlocationをデフォルトにしておけば問題ないかもしれませんが、私は asia-northeast1 にしてました

org.embulk.exec.PartialExecutionException: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `query_results' for nil:NilClass
  at org.embulk.exec.BulkLoader$LoaderState.buildPartialExecuteException(BulkLoader.java:340)
  at org.embulk.exec.BulkLoader.doRun(BulkLoader.java:566)
  at org.embulk.exec.BulkLoader.access$000(BulkLoader.java:35)
  at org.embulk.exec.BulkLoader$1.run(BulkLoader.java:353)
  at org.embulk.exec.BulkLoader$1.run(BulkLoader.java:350)
  at org.embulk.spi.Exec.doWith(Exec.java:22)
  at org.embulk.exec.BulkLoader.run(BulkLoader.java:350)
  at org.embulk.EmbulkEmbed.run(EmbulkEmbed.java:242)
  at org.embulk.EmbulkRunner.runInternal(EmbulkRunner.java:291)
  at org.embulk.EmbulkRunner.run(EmbulkRunner.java:155)
  at org.embulk.cli.EmbulkRun.runSubcommand(EmbulkRun.java:431)
  at org.embulk.cli.EmbulkRun.run(EmbulkRun.java:90)
  at org.embulk.cli.Main.main(Main.java:64)
Caused by: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `query_results' for nil:NilClass
  at RUBY.run(/root/.embulk/lib/gems/gems/embulk-input-bigquery-0.0.9/lib/embulk/input/bigquery.rb:88)
  at RUBY.run(uri:classloader:/gems/embulk-0.9.23-java/lib/embulk/input_plugin.rb:107)

sqlのキー間違い

  2020-07-30 04:01:01.176 +0000 [INFO] (0001:transaction): Loaded plugin embulk-input-bigquery (0.0.9)
org.embulk.exec.PartialExecutionException: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `encoding' for nil:NilClass
  at org.embulk.exec.BulkLoader$LoaderState.buildPartialExecuteException(BulkLoader.java:340)
  at org.embulk.exec.BulkLoader.doRun(BulkLoader.java:566)
  at org.embulk.exec.BulkLoader.access$000(BulkLoader.java:35)
  at org.embulk.exec.BulkLoader$1.run(BulkLoader.java:353)
  at org.embulk.exec.BulkLoader$1.run(BulkLoader.java:350)
  at org.embulk.spi.Exec.doWith(Exec.java:22)
  at org.embulk.exec.BulkLoader.run(BulkLoader.java:350)
  at org.embulk.EmbulkEmbed.run(EmbulkEmbed.java:242)
  at org.embulk.EmbulkRunner.runInternal(EmbulkRunner.java:291)
  at org.embulk.EmbulkRunner.run(EmbulkRunner.java:155)
  at org.embulk.cli.EmbulkRun.runSubcommand(EmbulkRun.java:431)
  at org.embulk.cli.EmbulkRun.run(EmbulkRun.java:90)
  at org.embulk.cli.Main.main(Main.java:64)
  Suppressed: java.lang.NullPointerException
    at org.embulk.exec.BulkLoader.doCleanup(BulkLoader.java:463)
    at org.embulk.exec.BulkLoader$3.run(BulkLoader.java:397)
    at org.embulk.exec.BulkLoader$3.run(BulkLoader.java:394)
    at org.embulk.spi.Exec.doWith(Exec.java:22)
    at org.embulk.exec.BulkLoader.cleanup(BulkLoader.java:394)
    at org.embulk.EmbulkEmbed.run(EmbulkEmbed.java:245)
    ... 5 more
Caused by: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `encoding' for nil:NilClass
  at RUBY.compile(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/erb.rb:599)
  at RUBY.initialize(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/erb.rb:801)
  at RUBY.transaction(/root/.embulk/lib/gems/gems/embulk-input-bigquery-0.0.9/lib/embulk/input/bigquery.rb:28)
  at RUBY.transaction(uri:classloader:/gems/embulk-0.9.23-java/lib/embulk/input_plugin.rb:58)

ymlで sql と指定すべきところを query としてました。
embulk-input-mysqlプラグインで使ってた別のファイルをコピーして持ってきたときに変え忘れていました。

embulk-input-bigqueryのインストールでエラー

$ embulk gem install embulk-input-bigquery

とやるとエラーが発生しました。

  ERROR:  Error installing embulk-input-bigquery:
  google-cloud-errors requires Ruby version >= 2.4.

依存している google-cloud-errorsRuby 2.4 以上である必要がありますが、 embulk v0.9.23(執筆時の最新安定版) は同梱しているjRubyが 9.1.15.0 です。
これはRuby 2.3 互換なので当然エラーになります。
ちなみに embulk v0.10.x でもだめでした。

エラーメッセージでググるhttps://github.com/medjed/embulk-input-bigquery/issues/33 にたどり着くのですが、 コメント欄を参考に

$ embulk gem install google-cloud-core -v 1.3.0

を実行しても

ERROR:  Error installing google-cloud-core:
  google-cloud-env requires Ruby version >= 2.4.

と、今度は別のパッケージがエラーを返します。
いろいろと試行錯誤するうちに下記の順番で一つづつインストールしていけば
成功することにたどり着きました。

$ embulk gem install faraday -v 0.17.3
$ embulk gem install multi_json -v 1.15.0
$ embulk gem install jwt -v 2.2.1
$ embulk gem install public_suffix -v 4.0.5
$ embulk gem install addressable -v 2.7.0
$ embulk gem install signet -v 0.11.0
$ embulk gem install declarative-option -v 0.1.0
$ embulk gem install declarative -v 0.0.20
$ embulk gem install uber -v 0.1.0
$ embulk gem install representable -v 3.0.4
$ embulk gem install retriable -v 3.1.2
$ embulk gem install mini_mime -v 1.0.2
$ embulk gem install memoist -v 0.16.2
$ embulk gem install os -v 1.1.0
$ embulk gem install googleauth -v 0.9.0
$ embulk gem install httpclient -v 2.8.3
$ embulk gem install google-api-client -v 0.32.1
$ embulk gem install google-cloud-env -v 1.2.1
$ embulk gem install google-cloud-core -v 1.3.0
$ embulk gem install embulk-input-bigquery

特定のディレクトの配下でgitのconfigを切り替える

リポジトリの状況

~/workspace
  |-- foo
  |   `-- foo-repo
  `-- bar
      |-- bar-repo1
      `-- bar-repo2

~/workspce に切り替えたい単位にディレクトリを作成しそれぞれリポジトリを置いてます。

ホーム配下

~/
  .gitconfig -> dotfiles/.gitconfig
  .gitconfig.local
  .gitconfig.foo
  .gitconfig.bar
  dotfiles
    `-- gitconfig

dotfiles(github管理)のしたに .gitconfig を作成しどこでも使うものはそこに記述します。 それをシンボリックリンクで、HOME配下に配置します。 PCごとに違う設定にしたいものは .gitconfig.local に。 .gitconfig.localincludeIf を使って案件ごとの設定を記述します。

.gitconfig

[include]
  path = .gitconfig.local

.gitconfig.local

[includeIf "gitdir:~/workspace/foo/"]
  path = .gitconfig.foo
[includeIf "gitdir:~/workspace/bar/"]
  path = .gitconfig.bar

.gitconfig.foo, .gitconfig.bar

[user]
    name=example
    email=example@example.com

手動で作成したAWS環境をterraformで後追い

AWSコンソールから手動で作成した環境をterraformで後追いするために必要なこと、やることをメモします。 コマンドの詳しい説明や使い方は省略します。

terraform

  • terraform init : 作業ディレクトリを初期化します。
  • terraform plan : 差分確認ようです。
  • terraform import : 既存環境をtfstateにインポートします。
  • terraform state rm : インポートを取り消したいときに使います。

terraform import

terraformはtfstateと呼ばれるファイルにterraformの実行後の環境を保存しています。 既存の環境をtfstateに書き込むことで、terraform plan を実行したときに差分がでなくなります。 あくまでtfstateに現在の状態を書き込むだけなので、 xxx.tf などのコードを作成してくれるわけではありません。

terraform import はリソースひとつひとつに対して実行していく必要があります。

下記のような感じで指定します。

$ terraform import <terraformのmodule名>.<リソース名(変数名)> <リソースを特定するIDなど>

ex)

$ terraform import aws_cloudwatch_log_group.test_group yada

リソースを特定するIDなど は、moduleによって指定するものが違って、nameだったりidだったりします。 terraform のドキュメントの最後のところに参考例があります。 実際に指定する値はAWSコンソールから取ってきます。

terraform state rm

terraform import でインポートしたものをtfstateから削除します。 tfstateからの削除なので、AWS環境には影響ありません。

terraforming

terraforming は、 terraform のコードを書き出してくれるものです。 terrafrom とは別物なので、別途インストールする必要があります。

terraform import と違って、リソースの種類を指定してそのすべてのコードを出力します。 例えばセキュリティグループを指定したら、アカウントに紐づくすべてのセキュリティグループを取ってきます。 便利ですが、対応していないリソースも多いのですべてを terraforming 任せにするわけにもいきません。

また、 terraforming には、 tfstate を管理するオプションもあります。 terraform import とどっちを使うかは、 既存の環境のどれくらいを terraform 化したいかによると思います。

流れ

今回は、既存AWS環境のうち一部を terraform 化したいので、 terraform import を使った方法を想定します。

といった感じです。

terraform init でtfstateを作成

特に気をつけることもないので割愛

リバースエンジニアリング

terraforming に対応しているものいないものがあるのでそれぞれ対応します。

対応しているもの

流れ

  • terraforming でコードの書き出し
  • 作成したtfファイルの中身を確認・修正
  • terraform import
  • 差分確認

terraforming でコードの書き出し

リソースごとにコードを書き出していきます。

例)

$ terraforming sg > sg.tf

作成したtfファイルの中身を確認・修正

  • いらないものは削除
  • 必要ならリソース名(変数名)を変更
    • terraforming が勝手に命名するので分かりやすいとは言えない
    • 自分の考える命名規則とあってないなど
    • 下記の foo の部分のこと
resource "aws_cloudwatch_log_group" "foo" {
  name = "Bar"
}

terraform import

terraforming には、tfstateも一緒に更新してくれるオプションがありますが、 今回は一部だけをterraform化するため、個別にインポートする方法を取ります。

差分確認

terraform plan で差分の確認をします。 差分が出るようならでなくなるまで修正します。

対応していないもの

まず、リソース用のtfファイルを用意して 下記のように空でリソース名(変数名)を書いておきます。

resource "aws_cloudwatch_log_group" "foo" {
}

このリソース名(変数名)を使って terraform import します。 インポートが成功したら、差分を確認して、差分が出なくなるまで修正を繰り返します。

aws_iam_group_policy_attachmentで複数の管理ポリシーを追加する

メモ

terraform v0.12.6以降

resource "aws_iam_group_policy_attachment" "foo_attach" {
  for_each = toset([
    "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess",
    "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess",
    "arn:aws:iam::aws:policy/IAMFullAccess",
    "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess",
  ])
  group      = aws_iam_group.foo.name
  policy_arn = each.value
}