Help us understand the problem. What is going on with this article?

AWS ECRにk8sで必要なイメージを持っていくの楽にする

AWSでKubernetesを運用する

少し前はEC2とかと、kubernetes構成管理ツール(kopsやらkubeadm)とかを使って構築していましたが最近ではEKS + ECRがデファクトスタンダードになってきたかなと思います。

またEKS + ECR構成にしたときは、Docker Imageは全てECRからpullしてきて、DockerHubからはpullしないようにするとかはよくあるかと思います。

これで運用しているときに地味に辛いのが、自分たちで作成していないimageやmanifestをデプロイする時だと思います。(kubernetes dashboard, prometheus, metric-server, etc...)

これらをデプロイしようとする場合は以下の手順を踏む必要があります。

  1. デプロイ用のマニフェストからimageの箇所を全て抜き出して, 外通できる場所でdocker pullする
  2. pullしたimageにecr用のtagをつける
  3. ecrにrepositoryを作成する
  4. tag付けしたimageをecrへpushする
  5. 元のマニフェストのimage箇所を書き換えてecrのimage pathに変更する
  6. manifestをapplyする

地味にめんどいです、2~4あたりを自動化するshellとかを作っていたのですが、1,5の作業もだいぶ辛くなってきた

これらの作業をまとめるツールを作ってみた

https://github.com/esakat/trimg

このあたりを楽にするCLIを作ってみました。

homebrewでインストールできます

$ brew install esakat/trimg/trimg

使い方

最初にあげた6つの手順のうち、1~4を実施するサブコマンドと、5を実施するサブコマンドを用意しました。

マニフェストからイメージ情報を抜き出して、docker pullして、tag付けして、ecrにリポジトリ作って、pushする

$ trimg transfer -f testfiles/input/replicaset.yml
[gcr.io/google_samples/gb-frontend:v3] [==============================================================================]  100 %
1: gcr.io/google_samples/gb-frontend:v3 transfer to <YourAccountId>.dkr.ecr.<YourDefaultRegion>.amazonaws.com/gcr.io/google_samples/gb-frontend:v3

ベースのマニフェストはこちらです
実行すると、見出しの通りのことをしてくれます
ECRにpushされています

$ aws ecr list-images --repository-name gcr.io/google_samples/gb-frontend
{
    "imageIds": [
        {
            "imageDigest": "sha256:60049e8aa1bb97242ce1a5fc5f9d86478d3f3407c2643edb054c717ac12c14bb",
            "imageTag": "v3"
        }
    ]
}

マニフェストからイメージ情報を抜き出して、ecrのpathに書き換える

$ trimg replace testfiles/input/replicaset.yml > replacedManifest.yml

コメント情報消えたり、keyの順番が少し変わったりするのでdiffでみてないです.

$ cat testfiles/input/replicaset.yml  | grep image:
        image: gcr.io/google_samples/gb-frontend:v3
$ cat replacedManifest.yml | grep image:
        image: <YourAccountId>.dkr.ecr.<YourDefaultRegion>.amazonaws.com/gcr.io/google_samples/gb-frontend:v3

kubectlと組み合わせる

trimgでmanifestを書き換え、必要なイメージをecrへ送ります

$ trimg transfer -f testfiles/input/replicaset.yml
$ trimg replace testfiles/input/replicaset.yml > replacedManifest.yml

EKSを用意しています

$ kubectl cluster-info | grep master
Kubernetes master is running at https://......eks.amazonaws.com

書き換えられたマニフェストをdeployします

$ kubectl apply -f replacedManifest.yml
replicaset.apps/frontend created

正常に起動して、ecrのimageを使って動いています

$ kubectl get replicaset
NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       97s

$ kubectl get replicaset frontend -ojson | jq .spec.template.spec.containers[0].image
"<YourAccountId>.dkr.ecr.<YourDefaultRegion>.amazonaws.com/gcr.io/google_samples/gb-frontend:v3"

シンプルなものですが、よかったら使ってもらえると嬉しいです

内部的なもの

これだけだと宣伝なので、内部でちょっとハマった箇所とかをまとめておきます。

DockerHubからのpullがgoでうまくできない

https://github.com/docker/docker を使ってgoのプログラムでdocker pullなどをやっていたのですが、なぜかDockerHubからのimage pullができませんでした(k8s.gcr.ioとかからはできた)

理由としては、DockerHubのイメージパスはデフォで省略されているようだった

docker pull nginx:latest 
-> docker pull docker.io/library/nginx:latest

docker.ioが省略されており、さらにDockerHub公式のイメージの場合はさらにlibraryを付与する必要があります。

以下で対応しています。
https://github.com/esakat/trimg/blob/master/pkg/image_transfer.go#L68-L84

この省略で拾えない場合は決まったエラーが返ってくるので
とりあえず元のイメージ名でpull, 省略で拾えないエラーだったら、省略されている文字を追加してあげてます。

Manifestからimageの箇所を抜き出す。

かなりごり押してます

まずmanifestを読み込むところですが、kubernetesの場合は1つのファイルに複数マニフェストが含まれるのはよくあります。
(これは別の記事で書いてます)

複数マニフェストを読み込んで[]map[interface{}]interface{}型の変数を得ます

最初はmanifest["kind"], manifest["apiVersion"]だけ読み込んで、対応する構造体(kubernetesリポジトリが提供している)にマッピングしようとしましたがうまくいきませんでした。

というのもkubernetesリポジトリが提供している構造体は全ての情報を含んでいるので(例えばcreationTimeとか)マッピングしたら余分な情報が初期値で作られてしまいます。

今回のアプリでは、image名だけ変えて、あとはそのままというのにする必要があったので、この方法は諦めて,以下のようにゴリ押しをしています。

manifest["spec"].(map[interface{}]interface{})["template"].(map[interface{}]interface{})....

これで掘っていくと、ネストがとても深くなります
一番長いCronJobだと以下のようになります

manifest["spec"].(map[interface{}]interface{})["template"].(map[interface{}]interface{})["spec"].(map[interface{}]interface{})["containers"].([]interface{})[i].(map[interface{}]interface{})["image"]

これを1つのkey毎に存在チェックしてあれば、次の処理へってネストするとコードのインデントが半端なくなります。
このあたりはutil化して再帰的にやるようにしています。
ここだけ抜き出したリポジトリがこちらです

関数型っぽい感じですが、head/tailみたいな感じの関数になっています

func DigYaml(y interface{}, keys ...interface{}) (interface{}, error) {
  head := keys[0]
  // y[head]を取得する処理 
  ..
  value := y[head]
  // y[head]の中身と可変長引数のtailを再帰する
  return DigYaml(value, keys[1:]...)
}

key1, err := dig_yaml.DigYaml(y, "hoge", 0, "key1")こんな感じで呼べます、これは次と同義ですyamlDoc["hoge"].([]interface{})[0].(map[interface{}]interface{})["key1"]

可変長引数はstringかintだけ受け付けて、stringならyamlのkeyとして判定, intの場合は配列のインデックスとして認識するようにしています
(数値型, 真偽型のkey含んでたらうまく動かないです..)


また対象のmanifestですが、image(PodTemplateSpec)を含むリソースが対象で、そのpod spec内のcontainers, initContainers配列で使われているimageが対象になります

リソースとかの仕様はAPI Documentで
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/

MultiProgressbarを使いたい

マニフェストからイメージ情報を抜き出して、docker pullして、tag付けして、ecrにリポジトリ作って、pushする という処理は全部やるとそこそこ時間かかります(でかいイメージだと2,3分かかったり)その間画面動かないのも嫌なのでプログレスバーを用意しようとしました。

go progress barで検索するといくつか候補がありますが

  • マルチプログレスバー対応
  • 今でも開発が続いている
  • Githubのstar数

から https://github.com/vbauerster/mpb を使うようにしました

[gcr.io/google_samples/gb-frontend:v3] [==============================================================================]  100 %

[gcr.io/google_samples/gb-frontend:v3]の右とかに,今やってるtask(docker pull中なのか, ecrへpush中なのか)を表示したいけど、一度progressbarを作っちゃうと、パーセンテージとか以外の情報は変えづらいのが少しだけ辛いとこですが、それ以外はとても使いやすいいいライブラリです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした