Golangを用いたマニフェスト(Kubernetes)生成ツール

注意事項

本記事ではgolangで実装したコマンドラインツールの紹介をしますが,私が構築したKubernetes環境に最適化(スペックの選択等)させているため,そのままでは使用することはできません.使用される際は自己責任でコードを書き換えて使用してください.

はじめに

本ブログではKubernetes(以下k8s) Clusterの整備に関する備忘録をまとめてきました.(最近更新できてない分は少しづつ更新していきます.)

現在はおおよそ図1のような構成でJupyterlabを提供することにより,機械学習環境をk8sを用いて構築しています.

fig1:around jupyterlab in k8s cluster
図1:k8s clusterのJupyterlab周辺

しかしながら,k8sを何も知らない人にとってはマニフェストの作成が使用への大きな壁になっていました.また,マニフェストのサンプルを配布した場合でも,スペックの異なるノードをまとめて運用しているためコンテナのスペックを選択する際に各ノードのスペックを知っておく必要があり,よりマニフェストの作成が複雑になっていました.

そこで今回,以下図2の赤枠部分を自動化するコマンドラインツールcksctlをgolangで実装してみました.使用方法などはGitLabのReadmeに書いてあるので,どんな感じで設計したのかをまとめておきたいと思います.

gitlab.com

fig2:pipline
図2:パイプライン

前提条件

図2のフローはGitOpsという思想に基づいたものになっています.GitOpsをざっくりいうと設定ファイル(k8sでいう所のマニフェスト)やプログラムファイルをGitで管理し,CIやCDをGitを起点に行おうというものです.図2をみて貰えばわかりますが,k8sを使用する場合,CIツールとCDツールを分離して従来のようなCIツールに権限が集中する状況を避けます.また,マニフェストを人間が管理し,kubectlコマンドを用いてアプリケーションのデプロイを行っているとマニフェストの管理が杜撰になり,デプロイミスも増えてしまいます.そこで,マニフェストをGit管理することでマニフェストの変更履歴を残し,CDツールでデプロイすることにより,オペレーションエラーを極力減らす事ができます.

ツールの選択

今回の環境では,アプリケーションの開発を行うわけではないため,CIはあまり使用しません.そのためCIを行うのは,Dockerfileのpushによるdocker buildのみになります.

CIツールにはGitLab CI,CDツールにはArgoCDを採用します.

環境

今回アプリケーションの開発も実際にアプリケーションを動作させる環境もDockerを用いて作成しました.これにより,開発環境と実行環境を揃える事ができ,さらにアプリケーションを使用する際もdocker runするだけで動作させる事ができます.

開発環境

FROM golang:1.13.10
LABEL maintainer="CVLAB.KubernetesService:tenzen"

ENV USER_NAME tenzen
ENV UID 1000
RUN useradd -m -u ${UID} ${USER_NAME}

ENV ARGO_CLI_VERSION v1.5.5

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPATH /home/${USER_NAME}/app

WORKDIR ${GOPATH}/src
RUN go get -u gopkg.in/src-d/go-git.v4/... \
 && go get gopkg.in/yaml.v3 \
 && curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/${ARGO_CLI_VERSION}/argocd-linux-amd64 \
 && chmod +x /usr/local/bin/argocd

実行環境

アプリケーションの実行環境では,開発環境でビルドした後のバイナリファイルのみを実行環境に持ってくるマルチステージビルドを行う事で,イメージ容量の軽減を実現しています.実際に開発環境のイメージは1GBほどありますが,アプリケーション環境は20MBほどにおさまっています.

FROM golang:1.13.10 as builder
LABEL maintainer="CVLAB.KubernetesService:tenzen"

ENV USER_NAME builder
ENV UID 1000
RUN useradd -m -u ${UID} ${USER_NAME}

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPATH /home/${USER_NAME}/app

WORKDIR ${GOPATH}/src
RUN go get -u gopkg.in/src-d/go-git.v4/... \
 && go get gopkg.in/yaml.v3 \
 && chown -R ${UID}:${UID} .
COPY ["builder", "."]

USER ${USER_NAME}
RUN go build -o cksctl main.go


FROM alpine:3.9.6

ENV BUILDER_NAME builder
ENV USER_NAME user
ENV UID 1000
RUN adduser -D -g '' --uid ${UID} ${USER_NAME}

ENV BUILD_DIR /home/${BUILDER_NAME}/app/src
ENV HOME_DIR /home/${USER_NAME}/app

WORKDIR ${HOME_DIR}
COPY --from=builder ["${BUILD_DIR}/cksctl", "."]
COPY --from=builder ["${BUILD_DIR}/YamlTemplates", "YamlTemplates"]

RUN apk update \
 && apk add curl \
 && curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v1.5.5/argocd-linux-amd64 \
 && chmod +x /usr/local/bin/argocd \
 && mkdir cksSettingFiles \
 && chown -R ${UID}:${UID} .

USER ${USER_NAME}
ENTRYPOINT [ "./cksctl" ]

実装

Jupyterlabをデプロイするまでの大きな流れとしては,

  1. GitLab CIでdockerイメージの作成
  2. cksctlでマニフェストを作成して,Gitに保存
  3. ArgoCDでk8s上にデプロイする.

となっています.

fig3:role of cksctl
図3:cksctlの役割

GitLab CI

1のGitLab CIで使用するgitlab-ci.ymlはサンプルを参考に以下のようなものを作成しました.

docker-build-master:
  image: docker:latest
  stage: build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - docker build --pull -t "$CI_REGISTRY_IMAGE" .
    - docker push "$CI_REGISTRY_IMAGE"
  only:
    - master

この設定ファイルを使用することによりDockerfileをpushすると自動的にコンテナイメージのビルドが始まります.

cksctl

2においてはユーザに入力された要求スペック情報などを元にHelm Chartのvalues.yamlを作成し,HelmChartとともにGit リポジトリにプッシュします. 例えばjupyterlabのvalues.yamlは以下のようになっており,golangのtext/templateを用いて値を書き込みます.

uname: {{ .uname }}
jupyter_pass: {{ .jupyter_pass }}
image_name: {{ .image_name }}
hardware_type: {{ .hardware_type }}
image_secret: {{ .image_secret }}
gpu_flag: {{ .gpu_flag }}
gpu_type: {{ .gpu_type }}
gpu_num: {{ .gpu_num }}
cpu_num: {{ .cpu_num }}
memory_capacities: {{ .memory_capacities }}
created_day: {{ .created_day }}

また,一度入力した情報を設定ファイルとして保存し,使用できるようにもなっています. 設定ファイルは以下のようになっており,values.yamlと同様にgolangのtext/templateを用いて書き込みます.

settings: 
- name: {{ .cks_proj_name }}
  metadata:
  - git:
    - uname: {{ .account_name }}
      token: {{ .access_token }}
      project: {{ .project_name }}
      type: {{ .git_type }}
    k8s:
    - basic_info:
      - uname: {{ .uname }}
        jupyter_pass: {{ .jupyter_pass }}
        image: {{ .image_name }}
        secret: {{ .image_secret }}
      spec:
      - gpu:
        - flag: true
          hardware: {{ .hardware_type }}
{{- if eq .gpu_flag "true" }}
          type: {{ .gpu_type }}
          num: {{ .gpu_num }}  
{{- end }}
        cpu:
        - num: {{ .cpu_num }}
        memory:
        - capacities: {{ .memory_capacities }}
        cephfs:
        - capacities: {{ .cephfs_capacities }}

ArgoCD

3では,ArgoCDがHelmChartを用いてjupyterをデプロイします. HelmChartを使用しているので,HelmChartを準備し,values.yamlgolangのtext/templateにのとった形式に変更すれば簡単に対応アプリケーションを 増やせるようになっています.

改善点

現状Gitのマスターブランチにしかpushできないので,別ブランチにもpushできるようにしていきたいと考えています.

最後に

今回初めてgolangを使ったのですが,はじめはJavaの知識が邪魔をして構造体やインターフェースを理解する事が大変でした.普段全くアプリケーション開発などしないため相当ひどいコードになっていると思うので,コードを見て発狂する可能性のある方は見ないことをお勧めします.