背景
あるシステムをkubernetesで動かすために、ローカル環境にあるDockerイメージをECRにプッシュする必要があって、aws-cliとdockerコマンドでのやり方は理解していたが、他のメンバーにaws-cli入れてもらうのとかシェルのスクリプト書くのもなんだかなーと思っていた。
そこで、そのシステムがRailsで動いているのでRubyでローカルでDockerイメージをビルドして、ECRにプッシュできないかなと考えていた。
概要
docker-apiでDockerイメージのビルドはできそうだったので、あとはaws-sdk-ecrのAws::ECR::Clientのput_imageかdocker-apiのDocker::ImageのpushでECRへのイメージのプッシュができそうだった。
aws-sdk-ecrのAws::ECR::Clientのput_imageの必須オプションでimage_manifestがあったのだけど、今まで扱ったことないし、docker-apiでも取得できなそうだった。
Dockerのドキュメント見たら、docker manifest関連のコマンド自体がexperimental(実験的)っぽいので、Aws::ECR::Clientのput_imageを使う方法は断念。
Docker::Imageのpushの方法は、Docker.authenticate!通せるかが肝になる。
aws ecr get-login --region region --no-include-emailでdocker login -u AWS -p password https://aws_account_id.dkr.ecr.us-east-1.amazonaws.comとdockerでECRにログインできる情報が取得できるので、Aws::ECR::Clientでなんとかその情報取れれば良さそう。
Aws::ECR::Clientのget_authorization_tokenのドキュメント見るとレスポンスの中のauthorizationTokenが
A base64-encoded string that contains authorization data for the specified Amazon ECR registry. When the string is decoded, it is presented in the format user:password for private registry authentication using docker login .
とdocker loginで使えるuser:passwordがbase64でエンコードされたものと書いてある。
要はbase64デコードするとAWS:[docker loginで使えるパスワード]が取得できる。
これで、Docker.authenticate!通せそう。
コード
require 'docker'
require 'aws-sdk'
aws_access_key_id, aws_secret_access_key = [何らかの方法で取得(環境によると思うので省略)]
Aws.config.update(credentials: Aws::Credentials.new(aws_access_key_id, aws_secret_access_key))
tag = '0.0.1' # 実際はYAMLに書くかコマンド引数にすべきだかここでは一旦変数で持つ
ecr_client = Aws::ECR::Client.new
# 既にECRにプッシュされたものがないか確認
list_images_response = ecr_client.list_images(repository_name: 'hoge')
pushed_image = list_images_response.image_ids.detect { |image_id| image_id.image_tag == tag }
if pushed_image
puts 'Pushed image is found. Nothing to do.'
return
end
# Docker.authenticate!に必要な情報だけでなくendpointもget_authorization_tokenで取得できる
authorization_token_response = ecr_client.get_authorization_token
authorization_token = authorization_token_response.authorization_data[0].authorization_token
proxy_endpoint = authorization_token_response.authorization_data[0].proxy_endpoint
full_repository_name = proxy_endpoint.sub(Regexp.escape('https://'), '') + '/hoge'
# 既にローカルにビルドされたイメージがあれば取得
builded_image = Docker::Image.all.detect { |image| image.info['RepoTags'].first == "#{full_repository_name}:#{tag}" }
unless builded_image
# ビルドしてタグ付け
puts 'Building image'
builded_image = Docker::Image.build_from_dir('.')
builded_image.tag('repo' => full_repository_name, 'tag' => tag)
end
# docker loginで使えるユーザー名とパスワードの取得
username, password = Base64.decode64(authorization_token).split(':')
options = {'username' => username, 'password' => password, 'serveraddress' => proxy_endpoint, 'email' => 'none'}
Docker.authenticate!(options)
# ECRにプッシュ
builded_image.push
実行
あとはDockerfileと同じディレクトリにpush_image_to_ecr.rbを配置して、bundle exec rails r push_image_to_ecr.rbするだけ(aws_access_key_id, aws_secret_access_keyの取得方法によってはruby push_image_to_ecr.rbでも)。