trimgというツールを作りました。

trimgというツールを作りました。

March 26, 2020
Cloud, Kubernetes
kubernetes, AWS, ECR

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 を作っちゃうと、パーセンテージとか以外の情報は変えづらいのが少しだけ辛いとこですが、それ以外はとても使いやすいいいライブラリです。