【GitHub】ssh.github.comからcloneできない
他の人が書いたスクリプトの中で、 git clone git@ssh.github.com:user/repo.git
のように ssh.github.com
からcloneする処理があったが、なぜかこれに失敗して困った。
error: couldn't make loader for git@ssh.github.com:user/repo.git: trouble cloning git@ssh.github.com:user/repo.git: exit status 128
接続確認してみるが、github.com
には接続できるものの ssh.github.com
は失敗する。
ssh -T git@github.com Hi Udomomo! You've successfully authenticated, but GitHub does not provide shell access.
ssh -T git@ssh.github.com git@ssh.github.com: Permission denied (publickey).
鍵が正しく作られていなければ両方失敗するはずなので、ローカルの id_rsa
を調べてみたところ、その置き場所に原因があった。
公開鍵認証をする際は、デフォルトでは .ssh/id_rsa
に秘密鍵を置く。しかし自分の場合、普段GitHubだけでなくawsなど他のサービス用の秘密鍵を管理しやすくするため、 .ssh/github/id_rsa
のようにディレクトリを分けてその中に秘密鍵を置いていた。秘密鍵のパスを替えた場合、 .ssh/config
内の IdentityFile
でパスを指定できるのだが、今回の場合は以下のようになっていた。
- github.com ->
.ssh/config
内でIdentityFile
を指定した状態で設定済み。そのため正しい秘密鍵を使って認証に成功。 - ssh.github.com ->
.ssh/config
内に設定がない。そのためデフォルトである.ssh/id_rsa
を探すものの秘密鍵がなく認証に失敗。
対応としては、 ssh.github.com
を .ssh/config
に追記すればよい。なお、github.com
への接続を HostName
を指定して ssh.github.com
に飛ばすだけでは解決できない。コマンドラインで与えられたホスト名は .ssh/config
内の Host
の項目のみを探索して判定されるため、 HostName
に書いてもマッチせず、 IdentityFile
の指定を渡すことができない。
Host github.com HostName ssh.github.com Port 443 IdentityFile ~/.ssh/github/id_rsa User git Host ssh.github.com Port 443 IdentityFile ~/.ssh/github/id_rsa User git
ssh -T git@github.com Hi Udomomo! You've successfully authenticated, but GitHub does not provide shell access.
ssh -T git@ssh.github.com Hi Udomomo! You've successfully authenticated, but GitHub does not provide shell access.
【Terraform】既存resourceの名前だけを変えたい
社内勉強会で教えてもらった内容。
Terraformで既存のresourceの名前を変えたいとき、普通にtfファイル内でresourceの名前を変更するだけだと、resource全体が作り直し(destory->create)されてしまう。
例えば以前の記事で使った aws_internet_gateway
resourceの名前を変更しようとすると、resource全体に差分が生じてしまう。
$ cat aws_internet_gateway.tf resource "aws_internet_gateway" "container-era-webapp-internet-gateway" { vpc_id = aws_vpc.container-era-webapp-vpc.id } $ cat aws_route.tf resource "aws_route" "container-era-webapp-public-route" { route_table_id = aws_route_table.container-era-webapp-public-route-table.id destination_cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.container-era-webapp-internet-gateway.id } # 名前をcontainer-era-webapp-gatewayに変更 $ cat aws_internet_gateway.tf resource "aws_internet_gateway" "container-era-webapp-gateway" { vpc_id = aws_vpc.container-era-webapp-vpc.id } $ cat aws_route.tf resource "aws_route" "container-era-webapp-public-route" { route_table_id = aws_route_table.container-era-webapp-public-route-table.id destination_cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.container-era-webapp-gateway.id } $ terraform plan Terraform will perform the following actions: # aws_internet_gateway.container-era-webapp-gateway will be created + resource "aws_internet_gateway" "container-era-webapp-gateway" { + arn = (known after apply) + id = (known after apply) + owner_id = (known after apply) + vpc_id = "vpc-01ff3b511d075fb99" } # aws_internet_gateway.container-era-webapp-internet-gateway will be destroyed - resource "aws_internet_gateway" "container-era-webapp-internet-gateway" { - arn = "arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:internet-gateway/igw-xxxxxxxxxxxx" -> null - id = "igw-xxxxxxxxxxxxxxx" -> null - owner_id = "xxxxxxxxxxxx" -> null - tags = {} -> null - vpc_id = "vpc-xxxxxxxxxxxx" -> null }
これが仮に本番環境で使われているリソースである場合、一時的にリソースが消えてしまい本番環境に影響が生じる可能性もある。
このような場合、 terraform state mv
コマンドを使い、tfstateファイルを変更する必要がある。
今回はlocalでtfstateファイルの変化を見たいので、まずbackendをS3からlocalに変える。
$ cat config.tf provider "aws" { region = "ap-northeast-1" } # terraform { # backend "s3" { # bucket = "container-era-terraform-udomomo" # key = "sample-app/vpc/terraform.tfstate" # region = "ap-northeast-1" # } # } $ terraform init ... Do you want to copy this state to the new "local" backend? Enter "yes" to copy and "no" to start with an empty state. Enter a value: yes
S3に置かれていたtfstateファイルがlocalにコピーされた。
terraform state mv
コマンドを試してみる。
$ terraform state mv aws_internet_gateway.container-era-webapp-internet-gateway aws_internet_gateway.container-era-webapp-gateway Move "aws_internet_gateway.container-era-webapp-internet-gateway" to "aws_internet_gateway.container-era-webapp-gateway" Successfully moved 1 object(s).
terraform state mv
コマンドを実行すると、自動でバックアップファイルが作られる。差分を見てみると以下のようになっている。
diff -u terraform.tfstate.1595057556.backup terraform.tfstate --- terraform.tfstate.1595057556.backup 2020-07-18 16:32:36.000000000 +0900 +++ terraform.tfstate 2020-07-18 16:32:36.000000000 +0900 @@ -1,7 +1,7 @@ { "version": 4, "terraform_version": "0.12.28", - "serial": 0, + "serial": 1, "lineage": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "outputs": { "cidr_block": { @@ -25,7 +25,7 @@ { "mode": "managed", "type": "aws_internet_gateway", - "name": "container-era-webapp-internet-gateway", + "name": "container-era-webapp-gateway", "provider": "provider.aws", "instances": [ { @@ -70,12 +70,7 @@ "transit_gateway_id": "", "vpc_peering_connection_id": "" }, - "private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "dependencies": [ - "aws_internet_gateway.container-era-webapp-internet-gateway", - "aws_route_table.container-era-webapp-public-route-table", - "aws_vpc.container-era-webapp-vpc" - ] + "private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ] },
旧 container-era-webapp-internet-gateway
に依存していた aws_route
resourceのdependenciesにもdiffがある。これはaws_internet_gatewayのみに変更を加えたためと思われる。公式レポジトリのissueによれば、tfstateファイルはどのような意図でリファクタリングが行われたかは一切関知しておらず、またdependenciesはresourceの削除時にしか使われないため、リファクタリング後に terraform apply
すれば影響がなくなるとのことだった。
terraform plan
で確認する。
$ terraform plan No changes. Infrastructure is up-to-date. This means that Terraform did not detect any differences between your configuration and real physical resources that exist. As a result, no actions need to be performed.
resourceに影響がないと確認できたので、backendをS3に戻し、mvされた後のtfstateファイルをS3にコピーする。
$ cat config.tf provider "aws" { region = "ap-northeast-1" } terraform { backend "s3" { bucket = "container-era-terraform-udomomo" key = "sample-app/vpc/terraform.tfstate" region = "ap-northeast-1" } } $ terraform init ... Do you want to overwrite the state in the new backend with the previous state? Enter "yes" to copy and "no" to start with the existing state in the newly configured "s3" backend. Enter a value: yes ... Terraform has been successfully initialized!
最後にtfファイルをapplyすればよい。
$ terraform apply ... Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
ちなみに、apply後にS3バケットに生成されていたtfstateファイルを確認したところ、先程diffが出ていた部分のdependenciesも正しく復活していた。
... "private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "dependencies": [ "aws_internet_gateway.container-era-webapp-gateway", "aws_route_table.container-era-webapp-public-route-table", "aws_vpc.container-era-webapp-vpc" ] ...
【MySQL】あるはずの権限が使えない場合はログインアカウントを確認する
先日MySQLにログインしたとき、付与されているはずの権限がなくクエリを投げられなかったことがある。ユーザ名は確かに合っていたのだが、問題はログイン元のホストにあった。
MySQLのユーザに権限を付与するとき、以下のようにユーザ名だけではなくホストも指定する。
GRANT SELECT, INSERT, UPDATE, DELETE ON db.test TO 'udomomo'@'10.254.0.0/255.255.240.0';
すなわち、ユーザ名は同じでも異なるホストからのログインに対して、異なる権限を設定することもできる。(権限付与の際にホストを指定しない場合、任意のホストとみなされる)
GRANT SELECT ON db.test TO 'udomomo';
このようなことができる理由は、mysqlではユーザ名とホスト名を含めて1つの固有なアカウントとみなしているため。 mysql.user
テーブルを確認すると、 'udomomo'@'10.254.0.0/255.255.240.0'
と 'udomomo'@'%'
は異なるアカウントとして記録されている。
SELECT user, host FROM mysql.user where user="udomomo"; +----------+---------------------------+ | user | host | +----------+---------------------------+ | udomomo | % | | udomomo | 10.254.0.0/255.255.240.0 | +----------+---------------------------+ 2 rows in set (0.00 sec)
ではユーザ名 udomomo
でログインすると、どちらのアカウントに該当するのだろうか。MySQLでは、ユーザ名のみで複数のアカウントに合致するときは、ホスト名の具体性が高い順に行をソートし、その順番にホストがマッチするか確認する。具体性とは %
のようなワイルドカード指定が使われていないものほど先に来るという意味である。すなわち、例えば10.254.0.1からユーザ名 udomomo
でログインすれば、必ず 'udomomo'@'10.254.0.0/255.255.240.0'
の方にマッチする。
今回の場合、間違って 10.254.0.0/255.255.240.0
に含まれないホストから udomomo
でログインしたことで、 INSERT
を行うことができなかった(当然パスワードは全く違う文字列なのだが、パスワードマネージャーを使っているため気づかなかった)。 このような時は、今ログインしているアカウントを確認するのが速い。 SELECT CURRENT_USER();
とすれば、今ログインしているアカウントがユーザ名・ホストともに表示される。
SELECT CURRENT_USER(); +--------------------------------+ | CURRENT_USER() | +--------------------------------+ | udomomo@10.254.0.0/255.255.240.0 | +--------------------------------+ 1 row in set (0.01 sec)
【terraform】importコマンドでtfstateファイルを複数モジュールに分割する
「コンテナ時代のWebサービスの作り方」を読みながらTerraformを触ってみている。
その中で、Terraformを使う例としてVPCとEC2を作成する項目があった。本ではまず1つのtfファイルに両方のリソースをまとめて記述した後、ec2とvpcでモジュールを分けた書き方を紹介している。
- パターンA: 1つのtfファイルに両方のリソースを記述する例 (tfstateファイルはS3バケットの
test/
以下に格納)
test |- sample.tf #VPC, Subnet, EC2リソースを定義
# test terraform apply sample.tf ... Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
- パターンB: リソースの種類ごとにモジュールを分ける例(tfstateファイルはS3バケットの
test/vpc/
test/ec2/
以下にそれぞれ格納)
test |- vpc | |- vpc.tf #VPC, Subnetリソースを定義 |- ec2 |- ec2.tf #EC2リソースを定義
ここで疑問に思ったのが、パターンA -> パターンBのように、もし最初に1つのtfファイルをapplyしその後でモジュールを分けたくなった場合どうするのかということ。というのも、パターンAとBでは tfstate
の格納場所が異なっているため、Aのtfファイルをapplyした後にBで分けた2つのtfファイルをapplyしようとしても、既にAで作られたtfstateファイルを検知できず、リソースが二重にapplyされかねない。
# test/vpc terraform plan ... Plan: 2 to add, 0 to change, 0 to destroy.
このような場合、 terraform import
を使って既存リソースの情報を取り込んでおく必要がある。
import
コマンドは ADDRESS
と ID
の2つの引数を必須としている。 ADDRESS
はtfファイル内で定義した resource
のアドレスを指す。例えば test/vpc/vpc.tf
内で my_vpc
という名前でVPCリソースを定義していた場合、 aws_vpc.my_vpc
と指定すればよい。(すなわち、 import
コマンドで既存のリソースを取り込むには、そのリソースに対応するtfファイルをあらかじめ作成しておかなければならない)
ID
の方は、作成済みのリソースを一意に識別できるようにプロバイダ上で割り振られた値を指す。値の確認方法は各プロバイダによるが、AWSのVPCであれば VPC ID
を、EC2であれば Instance ID
を使う。これらはAWSのコンソールやaws cliを使って確認できる。
これらを使って terraform import
を実行する。デフォルトではコマンドを実行したディレクトリにあるtfファイルが参照される。
# test/vpc terraform import aws_vpc.my_vpc vpc-xxxxxxxxxxxxxxxxx aws_vpc.my_vpc: Importing from ID "vpc-xxxxxxxxxxxxxxxxx"... aws_vpc.my_vpc: Import prepared! Prepared aws_vpc for import aws_vpc.my_vpc: Refreshing state... [id=vpc-xxxxxxxxxxxxxxxxx] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform. terraform import aws_subnet.my_subnet subnet-xxxxxxxxxxxxxxxxx aws_subnet.my_subnet: Importing from ID "subnet-xxxxxxxxxxxxxxxxx"... aws_subnet.my_subnet: Import prepared! Prepared aws_subnet for import aws_subnet.my_subnet: Refreshing state... [id=subnet-xxxxxxxxxxxxxxxxx] Import successful! The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform.
import
コマンドを実行するたびに、tfファイルの backend
等で指定された保存場所にtfstateファイルが作成される。直後に terraform plan
を行うと、差分がなくなっている。
# test/vpc
terraform plan
...
No changes. Infrastructure is up-to-date.
後はEC2の方も同様に行えば、余計な差分を生まずにtfstateファイルを移行できる。
import
コマンドは、現時点(terraform v0.12.28 + provider.aws v2.68.0)ではあくまでtfstateファイルを変更するのみで、tfファイルの自動生成はしてくれない。これを補ってくれるOSSツールもあるようだが、 公式ドキュメントには A future version of Terraform will also generate configuration.
と書いてあるので少しだけ期待している。
【bash】コマンドの戻り値をワンライナーで確認するときの注意
bashのスクリプトを書くとき、直前のコマンドの戻り値を見て条件分岐させたいことがよくある。戻り値は $?
で取得できるのだが、このコマンドはいかなる場合でも直前のコマンドしか考慮してくれない。
例えば、以下は $?
の誤った使い方である。
which aws | echo $?
この場合、 which aws | echo $?
で一つのコマンドとみなされるため、 $?
が表すのはその前のコマンドの戻り値となる。すなわち、パイプでつないだ前半部分のコマンドの戻り値を正しく判定できていない。例えば開発環境にsedコマンドが存在し、fooコマンドが存在しない場合、以下のようになる。
which foo # foo not found which sed | echo $? # 1
which sed # /usr/bin/sed which foo | echo $? # 0
正しくはパイプではなく以下のように使う必要がある。
which sed; echo $?
Kafka Dockerで$KAFKA_HOMEと$ZKの値を設定する
Kafka Dockerを使ってKafkaの実行環境を作っていたが、コンテナに接続してtopic一覧を表示しようとした際に、チュートリアルにある環境変数 $KAFKA_HOME
と $ZK
がどこで設定できるのかわからず詰まってしまった。
$KAFKA_HOME/bin/kafka-topics.sh --describe --topic topic --zookeeper $ZK
まず $KAFKA_HOME
だが、これは設定する必要がない。Kafka DockerのDockerfileを見てみると、ここで KAFKA_HOME=/opt/kafka
という記述がある。
次に $ZK
だが、これはGitHubレポジトリの start-kafka-shell.sh
経由でコンテナに接続した場合を想定した記法である。このシェルスクリプトの第2引数が、そのまま $ZK
の値として設定される。
ここには docker-compose.yml
で設定した KAFKA_ZOOKEEPER_CONNECT
の値を入れれば良い。 wurstmeister/zookeeper
を併用している場合、ホスト名は当該コンテナの hostname
を参照、ポートはデフォルトでは2181になる。
zookeeper: image: wurstmeister/zookeeper hostname: zookeeper kafka: .... environment: KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
なお、 start-kafka-shell.sh
はあくまでラッパースクリプトにすぎないので、 docker exec
で直接コンテナに接続しても問題ない。その場合、 $KAFKA_HOME
は使えるが $ZK
は使えないので直接指定する必要がある。
【Java】Nested Classの使いどころ
Javaで特定のクラスに強く紐づく小さなクラスがあるとき、それをネストさせている書き方と、ネストさせずに1ファイル内に並列して置く書き方の両方をよくみる。どちらを使うと良いのか迷うことが多かったので、Effective Javaの項目24にあたってみた。
まず、Nested Classには2種類ある。
- non-static nested class: 自身のインスタンスの中から、外部クラスの全フィールドにアクセス可能
- static nested class: 外部クラスのフィールドにはアクセスできない
このうちnon-static nested classの場合、自身をネストしているクラスのフィールドがたとえprivateであっても、そのフィールドにアクセス可能になる。
これをふまえると、Nested Classを宣言するときは、そのクラスがネストされているエンクロージングクラスからのみ使われ、かつ1メソッドとするには少し長すぎるという場合が良い。それが小さなクラスであれば、ネストさせた方が関係性が見えやすくなり、可読性も上がる。さらに、
では、non-static nested classとstatic nested classはどう使い分ければ良いだろうか。Nested classから見て、もしエンクロージングクラスのインスタンスやフィールドにアクセスすることがある場合、non-static nested classを使っても良い。non-static nested classを使う場合、Nested Classから使われているフィールドをprivateにでき、クラスのカプセル化を促進することができる。
しかし、エンクロージングクラスにアクセスすることが全く無い場合は、static nested classを使うことが推奨されている。non-static nested classの場合、エンクロージングクラスのオブジェクトと参照関係を持つので、それが全く使われずGCされると、エンクロージングクラスのオブジェクトが残ってしまいメモリリークを起こしうる。