GitLab CI で kaniko を使ってコンテナイメージを build / push

CI パイプラインは Docker コンテナ Runner で実行することが一般的なので、パイプラインの中で docker build するには privileged モードで Runner のコンテナを実行する必要があります。いわゆる DinD (Docker in Docker) です。

build:
  image: docker:latest
  script:
    - docker build -t registry.gitlab.com/kondoumh/sandbox -f Dockerfile .
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD registry.gitlab.com
    - docker push registry.gitlab.com/kondoumh/sandbox
  services:
    - docker:dind

kaniko は Google 謹製のコンテナイメージビルドツールです*1

github.com

Docker daemon に依存せず、コンテナ内、もしくは Kubernetes cluster の Pod 内で Dockerfile からイメージをビルドできるため、DinD を回避しセキュアに運用できます。

GitLab CI でも kaniko の利用ガイドがありました。

https://docs.gitlab.com/ee/ci/docker/using_kaniko.html

/kaniko/.docker/config.json に Container Registry とそのユーザー名、パスワードを設定し、kaniko executer に Dockerfile や push 先などのコンテキストを渡して実行します。kaniko のイメージは debug タグを指定する必要があります。

build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"registry.gitlab.com\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG

docker:dind を services に指定する必要はありません。実行するとちゃんとイメージが push されました。

f:id:kondoumh:20191228145020p:plain

gitlab.com もいつの間にか Container Registry 提供していたんですね。

*1:蟹工船に由来するネーミング?

Electron アプリの通知 on Windows

Electron は Notification をサポートしていて、Renderer プロセスでも Main プロセスでも表示できます。

野良 Scrapbox アプリで GitHub API 使って latest release を取得し現在のバージョンと違っていたら通知を出し、クリックしたらダウンロードページを開くようコードを追加しました。

f:id:kondoumh:20191221123026p:plain

macOS では普通に動きました。Windows では「しかし何も起こらなかった。」

公式ドキュメントを見ると Application User Model IDs をスタートメニューにインストールしなくてはいけないとか、 Squirrel という更新フレームワークに依存してればちょっとワークアラウンドが省略できるとか書いてあり、要するに Windows のサポートは面倒ということがわかりました。更新フレームワークの導入はオーバーな気がしたので、通知だけ使いたかったのです。

https://electronjs.org/docs/tutorial/notifications#windows

NPM にも通知関連のパッケージがたくさんあり、いろんな人が苦労してそうな感じがしました。

macOS の方はアプリケーション起動時に一度許可すれば、以降は通知してくれます。

ということで、Electron 側の対応を待つことにして Windows 対応は諦めました。

Kubeflow が MicroK8s の Addon になってた

以前 Kubeflow を MiniKF (Minikube ベースの VirtualBox イメージ) で導入しました。

blog.kondoumh.com

その後 Ubuntu 19.10 のリリース時、Kubeflow が MicroK8s の Addon として導入できるようになるというアナウンスがありました。

ubuntu.com

MicroK8s は Snap で簡単にインストールできる Kubernetes 環境です。Addon として Dashboard、CoreDNS、Istio などを microk8s.enable コマンドで簡単に導入できます。

blog.kondoumh.com

そして、つい先日の MicroK8s 1.17 - 11 December 2019 で Addon として追加された模様です。

New addon: kubeflow. Give it a try with microk8s.enable kubeflow.

Release notes | MicroK8s

MicroK8s なので Ubuntu 19.10 が必須というわけでもなさそうですが、環境を用意して導入してみました。

まず Snap で MicroK8s をインストール

$ sudo snap install microk8s --classic
microk8s v1.17.0 from Canonical✓ installed

ユーザーを microk8s グループに追加してログインしなおし。

$ sudo usermod -a -G microk8s hoge

ステータスを確認。kubeflow は disable になっています。

$ microk8s.status --wait-ready
microk8s is running
addons:
cilium: disabled
dashboard: disabled
dns: disabled
fluentd: disabled
gpu: disabled
helm: disabled
ingress: disabled
istio: disabled
jaeger: disabled
juju: disabled
knative: disabled
kubeflow: disabled
linkerd: disabled
metallb: disabled
metrics-server: disabled
prometheus: disabled
rbac: disabled
registry: disabled
storage: disabled

microk8s.kubectl を kubectl として実行できるよう alias を設定

$ sudo snap alias microk8s.kubectl kubectl
Added:
  - microk8s.kubectl as kubectl

kubeflow を enable します。dns , storage など依存するアドオンも enable され最後に kubeflow のデプロイが開始されます。

Pod が大量に起動され、Service が Ready になるまでに 30分ほどかかりました。

$ microk8s.enable kubeflow
Enabling dns...
Enabling storage...
Enabling dashboard...
Enabling ingress...
Enabling rbac...
Enabling juju...
Deploying Kubeflow...
Kubeflow deployed.
Waiting for operator pods to become ready.
Waited 0s for operator pods to come up, 28 remaining.
Waited 15s for operator pods to come up, 27 remaining.
Waited 30s for operator pods to come up, 27 remaining.
Waited 45s for operator pods to come up, 27 remaining.
Waited 60s for operator pods to come up, 25 remaining.
Waited 75s for operator pods to come up, 21 remaining.
Waited 90s for operator pods to come up, 19 remaining.
Waited 105s for operator pods to come up, 18 remaining.
Waited 120s for operator pods to come up, 17 remaining.
Waited 135s for operator pods to come up, 15 remaining.
Waited 150s for operator pods to come up, 13 remaining.
Waited 165s for operator pods to come up, 13 remaining.
Waited 180s for operator pods to come up, 11 remaining.
Waited 195s for operator pods to come up, 10 remaining.
Waited 210s for operator pods to come up, 8 remaining.
Waited 225s for operator pods to come up, 4 remaining.
Waited 240s for operator pods to come up, 3 remaining.
Operator pods ready.
Waiting for service pods to become ready.

Congratulations, Kubeflow is now available.
The dashboard is available at https://localhost/

    Username: admin
    Password: XXXXXXXXXXXXXXXXXXXXXXX

To see these values again, run:
    microk8s.juju config kubeflow-gatekeeper username
    microk8s.juju config kubeflow-gatekeeper password


To tear down Kubeflow and associated infrastructure, run:
   microk8s.disable kubeflow

kubeflow というnamespace が作られ、多くの deployment や service が作られています。0.5 の時よりかなり増えてる感じです。

$ kubectl get deploy,svc -n kubeflow
NAME                                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ambassador                    1/1     1            1           19m
deployment.apps/argo-controller               1/1     1            1           16m
deployment.apps/argo-ui                       1/1     1            1           19m
deployment.apps/jupyter-controller            1/1     1            1           19m
deployment.apps/jupyter-web                   1/1     1            1           18m
deployment.apps/katib-controller              1/1     1            1           18m
deployment.apps/katib-manager                 1/1     1            1           17m
deployment.apps/katib-ui                      1/1     1            1           17m
deployment.apps/kubeflow-dashboard            1/1     1            1           17m
deployment.apps/kubeflow-gatekeeper           1/1     1            1           17m
deployment.apps/kubeflow-login                1/1     1            1           17m
deployment.apps/kubeflow-profiles             1/1     1            1           17m
deployment.apps/metacontroller                1/1     1            1           17m
deployment.apps/metadata                      1/1     1            1           15m
deployment.apps/metadata-ui                   1/1     1            1           14m
deployment.apps/modeldb-backend               1/1     1            1           15m
deployment.apps/modeldb-store                 1/1     1            1           16m
deployment.apps/modeldb-ui                    1/1     1            1           15m
deployment.apps/pipelines-api                 1/1     1            1           15m
deployment.apps/pipelines-persistence         1/1     1            1           15m
deployment.apps/pipelines-scheduledworkflow   1/1     1            1           15m
deployment.apps/pipelines-ui                  1/1     1            1           14m
deployment.apps/pipelines-viewer              1/1     1            1           14m
deployment.apps/pytorch-operator              1/1     1            1           14m
deployment.apps/tf-job-dashboard              1/1     1            1           15m
deployment.apps/tf-job-operator               1/1     1            1           15m

NAME                                           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
service/ambassador                             ClusterIP   10.152.183.73    <none>        80/TCP              19m
service/ambassador-operator                    ClusterIP   10.152.183.100   <none>        30666/TCP           19m
service/argo-controller-operator               ClusterIP   10.152.183.215   <none>        30666/TCP           19m
service/argo-ui                                ClusterIP   10.152.183.55    <none>        8001/TCP            19m
service/argo-ui-operator                       ClusterIP   10.152.183.134   <none>        30666/TCP           19m
service/jupyter-controller-operator            ClusterIP   10.152.183.104   <none>        30666/TCP           19m
service/jupyter-web                            ClusterIP   10.152.183.191   <none>        5000/TCP            18m
service/jupyter-web-operator                   ClusterIP   10.152.183.205   <none>        30666/TCP           19m
service/katib-controller                       ClusterIP   10.152.183.200   <none>        443/TCP             18m
service/katib-controller-operator              ClusterIP   10.152.183.189   <none>        30666/TCP           18m
service/katib-db                               ClusterIP   10.152.183.45    <none>        3306/TCP            18m
service/katib-db-endpoints                     ClusterIP   None             <none>        <none>              18m
service/katib-db-operator                      ClusterIP   10.152.183.48    <none>        30666/TCP           18m
service/katib-manager                          ClusterIP   10.152.183.222   <none>        6789/TCP            17m
service/katib-manager-operator                 ClusterIP   10.152.183.167   <none>        30666/TCP           18m
service/katib-ui                               ClusterIP   10.152.183.22    <none>        80/TCP              17m
service/katib-ui-operator                      ClusterIP   10.152.183.199   <none>        30666/TCP           18m
service/kubeflow-dashboard                     ClusterIP   10.152.183.237   <none>        8082/TCP            17m
service/kubeflow-dashboard-operator            ClusterIP   10.152.183.66    <none>        30666/TCP           18m
service/kubeflow-gatekeeper                    ClusterIP   10.152.183.240   <none>        8085/TCP            17m
service/kubeflow-gatekeeper-operator           ClusterIP   10.152.183.2     <none>        30666/TCP           17m
service/kubeflow-login                         ClusterIP   10.152.183.31    <none>        5000/TCP            17m
service/kubeflow-login-operator                ClusterIP   10.152.183.252   <none>        30666/TCP           17m
service/kubeflow-profiles                      ClusterIP   10.152.183.239   <none>        8081/TCP            17m
service/kubeflow-profiles-operator             ClusterIP   10.152.183.23    <none>        30666/TCP           18m
service/metacontroller                         ClusterIP   10.152.183.112   <none>        9999/TCP            17m
service/metacontroller-operator                ClusterIP   10.152.183.13    <none>        30666/TCP           18m
service/metadata                               ClusterIP   10.152.183.164   <none>        8080/TCP            15m
service/metadata-db                            ClusterIP   10.152.183.209   <none>        3306/TCP            16m
service/metadata-db-endpoints                  ClusterIP   None             <none>        <none>              16m
service/metadata-db-operator                   ClusterIP   10.152.183.67    <none>        30666/TCP           16m
service/metadata-operator                      ClusterIP   10.152.183.121   <none>        30666/TCP           15m
service/metadata-ui                            ClusterIP   10.152.183.182   <none>        3000/TCP            14m
service/metadata-ui-operator                   ClusterIP   10.152.183.6     <none>        30666/TCP           16m
service/minio                                  ClusterIP   10.152.183.213   <none>        9000/TCP            16m
service/minio-endpoints                        ClusterIP   None             <none>        <none>              16m
service/minio-operator                         ClusterIP   10.152.183.136   <none>        30666/TCP           17m
service/modeldb-backend                        ClusterIP   10.152.183.69    <none>        8085/TCP,8080/TCP   15m
service/modeldb-backend-operator               ClusterIP   10.152.183.94    <none>        30666/TCP           16m
service/modeldb-db                             ClusterIP   10.152.183.155   <none>        3306/TCP            15m
service/modeldb-db-endpoints                   ClusterIP   None             <none>        <none>              15m
service/modeldb-db-operator                    ClusterIP   10.152.183.56    <none>        30666/TCP           16m
service/modeldb-store                          ClusterIP   10.152.183.20    <none>        8086/TCP            16m
service/modeldb-store-operator                 ClusterIP   10.152.183.224   <none>        30666/TCP           17m
service/modeldb-ui                             ClusterIP   10.152.183.7     <none>        3000/TCP            15m
service/modeldb-ui-operator                    ClusterIP   10.152.183.130   <none>        30666/TCP           16m
service/pipelines-api                          ClusterIP   10.152.183.43    <none>        8887/TCP,8888/TCP   15m
service/pipelines-api-operator                 ClusterIP   10.152.183.34    <none>        30666/TCP           16m
service/pipelines-db                           ClusterIP   10.152.183.53    <none>        3306/TCP            16m
service/pipelines-db-endpoints                 ClusterIP   None             <none>        <none>              16m
service/pipelines-db-operator                  ClusterIP   10.152.183.32    <none>        30666/TCP           16m
service/pipelines-persistence-operator         ClusterIP   10.152.183.161   <none>        30666/TCP           15m
service/pipelines-scheduledworkflow-operator   ClusterIP   10.152.183.74    <none>        30666/TCP           15m
service/pipelines-ui                           ClusterIP   10.152.183.68    <none>        3000/TCP,3001/TCP   14m
service/pipelines-ui-operator                  ClusterIP   10.152.183.135   <none>        30666/TCP           15m
service/pipelines-viewer                       ClusterIP   10.152.183.61    <none>        8001/TCP            14m
service/pipelines-viewer-operator              ClusterIP   10.152.183.25    <none>        30666/TCP           15m
service/pytorch-operator-operator              ClusterIP   10.152.183.208   <none>        30666/TCP           14m
service/tf-job-dashboard                       ClusterIP   10.152.183.179   <none>        8080/TCP            15m
service/tf-job-dashboard-operator              ClusterIP   10.152.183.49    <none>        30666/TCP           15m
service/tf-job-operator-operator               ClusterIP   10.152.183.241   <none>        30666/TCP           15m

ブラウザで localhost に接続するとログイン画面が出るのでインストール完了時に表示された admin ユーザとパスワードでログインします。パスワードは microk8s.juju コマンドで再表示可能です。

$ microk8s.juju config kubeflow-gatekeeper password

おなじみの kubeflow の dashboard が表示されました。

f:id:kondoumh:20191215111758p:plain

v0.6.0-rc.0 が入ったようです。現在最新版は 0.7 で次は 1.0 になるみたいです。

kubeflow/ROADMAP.md at master · kubeflow/kubeflow · GitHub

ということで、MicroK8s の近い将来のリリースで Kubeflow 1.0 が入るようになるでしょう。

Kubeflow は導入障壁がまだ高いのですが、MicroK8s の Addon になるとかなり手軽になりますね。Kubeflow を利用する開発環境の構築にも良さそうです。今回は、GCP に 8 vCPU 30GB メモリという強めな VM を用意し GNOME と xrdp でリモートデスクトップ接続しました。Core i7 で 32GB メモリ程度の LInux マシンが用意できればけっこう使える環境が作れそうです。

Argo CD で GitOps 環境を構築する on GKE

GitOps というアプリデプロイ戦略が Cloud Native な CD *1 で普及し始めています。

www.weave.works

ソースコードのリポジトリとは別にアプリの設定用リポジトリを用意し、リポジトリの状態とデプロイの状態を一致させるようにツールを利用して管理します。これにより設定の変更を GitHub(GitLab) flow に載せることができ、オペミスやスクリプトのバグによる事故を防ぐことが可能になるとのことです。

GitOps の CD ツールの一つである Argo CD を GKE で使ってみました。

argoproj.github.io

GKE のクラスタを作って credentials を設定し kubectl で操作できるようにします。

$ gcloud container clusters get-credentials xxx-cluster --zone asia-northeast-a --project xxxxx

接続を確認。ノード2つのクラスタを作りました。

$ kubectl get node

NAME                                                STATUS   ROLES    AGE   VERSION
gke-standard-cluster-1-default-pool-xxxxxxxxxxxxxxx   Ready    <none>   11m   v1.13.11-gke.14
gke-standard-cluster-1-default-pool-xxxxxxxxxxxxxxx   Ready    <none>   11m   v1.13.11-gke.14

Argo CD が動作するための namespace argocd を作ります。

$ kubectl create namespace argocd
namespace/argocd created

Argo CD の現時点の最新リリース 1.3.6 のマニフェストを使ってインストールします*2

Release v1.3.6 · argoproj/argo-cd · GitHub

$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v1.3.6/manifests/install.yaml

customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created
serviceaccount/argocd-application-controller created
serviceaccount/argocd-dex-server created
serviceaccount/argocd-server created
role.rbac.authorization.k8s.io/argocd-application-controller created
role.rbac.authorization.k8s.io/argocd-dex-server created
role.rbac.authorization.k8s.io/argocd-server created
clusterrole.rbac.authorization.k8s.io/argocd-application-controller created
clusterrole.rbac.authorization.k8s.io/argocd-server created
rolebinding.rbac.authorization.k8s.io/argocd-application-controller created
rolebinding.rbac.authorization.k8s.io/argocd-dex-server created
rolebinding.rbac.authorization.k8s.io/argocd-server created
clusterrolebinding.rbac.authorization.k8s.io/argocd-application-controller created
clusterrolebinding.rbac.authorization.k8s.io/argocd-server created
configmap/argocd-cm created
configmap/argocd-rbac-cm created
configmap/argocd-ssh-known-hosts-cm created
configmap/argocd-tls-certs-cm created
secret/argocd-secret created
service/argocd-dex-server created
service/argocd-metrics created
service/argocd-redis created
service/argocd-repo-server created
service/argocd-server-metrics created
service/argocd-server created
deployment.apps/argocd-application-controller created
deployment.apps/argocd-dex-server created
deployment.apps/argocd-redis created
deployment.apps/argocd-repo-server created
deployment.apps/argocd-server created

デプロイが完了したので、argocd の namespace を覗いてみます。

$ kubectl get deploy,pod,svc -n argocd

NAME                                                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/argocd-application-controller   1/1     1            1           3m29s
deployment.extensions/argocd-dex-server               1/1     1            1           3m29s
deployment.extensions/argocd-redis                    1/1     1            1           3m29s
deployment.extensions/argocd-repo-server              1/1     1            1           3m29s
deployment.extensions/argocd-server                   1/1     1            1           3m28s

NAME                                                 READY   STATUS    RESTARTS   AGE
pod/argocd-application-controller-59b7c6958d-676kv   1/1     Running   0          3m29s
pod/argocd-dex-server-658745cd65-kks5t               1/1     Running   0          3m29s
pod/argocd-redis-fc585c648-nr7z2                     1/1     Running   0          3m29s
pod/argocd-repo-server-6746fdc4dd-64c2x              1/1     Running   0          3m29s
pod/argocd-server-85b5959ffb-st7xt                   1/1     Running   0          3m28s

NAME                            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)             AGE
service/argocd-dex-server       ClusterIP   10.4.1.108    <none>        5556/TCP,5557/TCP   3m30s
service/argocd-metrics          ClusterIP   10.4.5.166    <none>        8082/TCP            3m30s
service/argocd-redis            ClusterIP   10.4.3.179    <none>        6379/TCP            3m30s
service/argocd-repo-server      ClusterIP   10.4.8.63     <none>        8081/TCP,8084/TCP   3m30s
service/argocd-server           ClusterIP   10.4.7.233    <none>        80/TCP,443/TCP      3m29s
service/argocd-server-metrics   ClusterIP   10.4.15.178   <none>        8083/TCP            3m30s

外部から Argo CD に接続できるよう、LoadBalancer を設定します。

$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

しばらく待つと GCP のネットワークサービスに検知されてロードバランサーが作成され、argocd-server に EXTERNAL-IP が割り当てられます。

$ kubectl get svc argocd-server -n argocd
NAME            TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)                      AGE
argocd-server   LoadBalancer   10.4.7.233   xx.xx.xx.xx   80:30669/TCP,443:31636/TCP   16m

割り当てられた IP にブラウザでアクセスすると、Argo CD のログイン画面がでます*3

f:id:kondoumh:20191214104659p:plain

admin のパスワードは argocd-server の pod 名になっていますので、kubectl で取得してログインします。

f:id:kondoumh:20191214105054p:plain

今回はアプリを作る手間を省くため Argo CD の example リポジトリをフォークして GitOps の設定をします。

github.com

Argo CD の Setting -> Repositories を開きます。

f:id:kondoumh:20191214111337p:plain

public なリポジトリとしてフォークしたので認証情報なしで CONNECT REPO USING HTTPS に URL を設定するだけです。

f:id:kondoumh:20191214111539p:plain

接続に成功しました。

f:id:kondoumh:20191214111628p:plain

デプロイするアプリ用の namespace を作っておきます。

$ kubectl create ns prod
namespace/prod created

CREATE NEW APP を クリックして、デプロイ対象のアプリ情報を登録します。example の helm-guestbook をデプロイするので、先に登録したリポジトリを選択して Path で helm-guestbook を選択。

f:id:kondoumh:20191214112223p:plain

アプリの namespace は先ほど作成した prod を指定。

f:id:kondoumh:20191214112424p:plain

CREATE をクリックすると GitHub との Sync が開始されデプロイが始まります。

f:id:kondoumh:20191214112557p:plain

kubectl で確認。すでに ロードバランサーも作られてアプリがデプロイされています。

$ kubectl get deployment,svc,pod -n prod
NAME                                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/guestbook-helm-guestbook   2/2     2            2           4m2s

NAME                               TYPE           CLUSTER-IP   EXTERNAL-IP    PORT(S)        AGE
service/guestbook-helm-guestbook   LoadBalancer   10.4.11.37   xx.xx.xx.xxx   80:32362/TCP   4m3s

NAME                                            READY   STATUS    RESTARTS   AGE
pod/guestbook-helm-guestbook-764f9f8885-bqzpz   1/1     Running   0          4m2s
pod/guestbook-helm-guestbook-764f9f8885-hqvpd   1/1     Running   0          4m2s

アプリの IP にアクセスすると確かに起動しています。

f:id:kondoumh:20191214113433p:plain

Argo CD でアプリの詳細を見ると、kubectl で取得できる情報がグラフィカルに閲覧可能です。

f:id:kondoumh:20191214113535p:plain

グラフの各ノードをクリックすると、詳細情報が出て、状態、マニフェスト、イベント、ログなどを確認できます。

f:id:kondoumh:20191214114410p:plain

GitOps なので、設定のリポジトリを更新するとそれに応じてデプロイされたアプリの状態も変化します。

ということで、フォークしたリポジトリの設定ファイルで replicaCount を増やしてみます。

f:id:kondoumh:20191214114639p:plain

3分ほど待っていると、Pod が1個増えました。

f:id:kondoumh:20191214115010p:plain

kubectl でも確認できます。

$ kubectl get pod -n prod
NAME                                        READY   STATUS    RESTARTS   AGE
guestbook-helm-guestbook-764f9f8885-bqzpz   1/1     Running   0          29m
guestbook-helm-guestbook-764f9f8885-hqvpd   1/1     Running   0          29m
guestbook-helm-guestbook-764f9f8885-rfb6c   1/1     Running   0          4m25s

以上のように Argo CD は Git リポジトリとアプリのデプロイ状態を同期させることができます。

リアクティブな Web UI が使いやすいですが、もちろん argocd という CLI も提供されています。Helm chart 以外にも Kustomize / ksonnet などのパッケージにも対応しており、個別のツールをインストールしなくてもデプロイできます。

ビルド・テストやコンテナイメージの作成を CI で実施し GitOpt ツールを CD に適用すれば、開発からデプロイまでの自動化を実現できそうです。

*1:継続的デリバリ

*2:Helm chart によるインストールも可能ですが、今回は素のマニフェストを使いました。

*3:TLS 設定がないので警告が出ますが

iPad でコードを書く Late 2019

ラップトップに比べるとかなり軽量な iPad。Cellular モデルなら出先や新幹線に持ち込んですぐコードが書けます。Magic Keyboard をペアリングしてるのでタイピングもノンストレスです。

blog.kondoumh.com

Blink で Ubuntu の仮想マシンに接続して Tmux + Emacs で使っています。macOS と同様 Zsh にスイッチしました。数年前に比べるとターミナルアプリも進化しました。Node.js や Go で CLI アプリ書く環境としては完成度高いです。iPadOS でマルチタスクが改善され、9.7インチ iPad でもブラウザとのタイル表示が使いやすくなりました。

f:id:kondoumh:20191130002056p:plain

そして今は何と言っても Gitpod でしょう。

blog.kondoumh.com

ほぼフルスペックの VS Code がブラウザの1タブ内に起動します。バックグラウンドに回ってもサーバー側でリサイクルされない限り作業を継続できます。CLI はもちろん Web UI 開発もできてしまいます*1

f:id:kondoumh:20191130094029p:plain

ちなみに Chrome だとターミナルへのコマンド入力中にブラウザのツールバーが出て被ってしまうので Safari で使う方がよいです。また Gitpod からのリポジトリへの push は設定ページで有効化する必要があります。

https://gitpod.io/access-control/

f:id:kondoumh:20191130195110j:plain:w350

先日 public beta が公開された Visual Studio Online。

visualstudio.microsoft.com

ベータテストには登録してますがまだ全然使ってません。iPadOS の Safari で起動するとちゃんと動作はしてそうです。「このブラウザは未対応だけど、すぐにサポート対象に追加するよ」というメッセージが出ているので期待できそうです。

f:id:kondoumh:20191201111119p:plain

以上のように、iPad は開発用のモバイル端末としてかなり実用的になってきていると感じます。僕はいまだに iPad Air 2 (2014年発売) を使っていますが、第7世代 iPad や第3世代 iPad Air ならブラウザも高速動作するし画面もちょっと大きくなってるのでさらに快適でしょう。

*1:Developer ツールが使えないのでデバッグはできませんが。

Emacs の go-mode 設定

最近わりと Go 書くようになってきたので Emacs の Go 環境を作ってみました。

company を使っているのでコード補完は company-go、godoc の表示は go-eldoc にしてみました。

~/.emacs.d/init.el の設定。タブ幅は2にしてます。保存時に gofmt かけています。

;; go-mode                                                                   
(use-package go-mode)
(add-hook 'go-mode-hook
          '(lambda ()
             (setq tab-width 2)))
(add-hook 'go-mode-hook 'go-eldoc-setup)
(add-hook 'before-save-hook 'gofmt-before-save)
(add-to-list 'company-backends 'company-go)

補完効いてミニバッファに API のシグネチャが表示されます。

f:id:kondoumh:20191128233400p:plain

goimports 使えばフォーマットついでに未使用の import 消してくれますが、自動保存にしているため追加直後に消されることになるので gofmt にしてます。

Terraform と GItHub Actions で DigitalOcean Droplets の CI/CD を作る

VPS は DigitalOcean の Droplets が安いのでたまに利用しています。

www.digitalocean.com

Terraform は DigitalOcean に対応していて専用のプラグインが HashiCorp から提供されています。

www.terraform.io

Terraform 用の GitHub Actions も公開されていました。

GitHub - hashicorp/terraform-github-actions: Terraform GitHub Actions

Droplet は Web 画面をポチポチして作るのも簡単ですが、Terraform の勉強を兼ねて Actions で CI/CD することにしました。

Droplet を作る tf ファイル。5$ / mo のマシンを指定しています。DigitalOcean プロバイダーを定義しておくと、terraform init 実行時に必要なプラグインをダウンロードしてくれます。API トークンは環境変数 DIGITALOCEAN_TOKEN に展開しておきます。

provider "digitalocean" {
  # export DIGITALOCEAN_TOKEN="Your API TOKEN"
}

data "digitalocean_ssh_key" "ssh_key" {
  name = "blink"
}

resource "digitalocean_droplet" "dev" {
  image = "${var.ubuntu}"
  name = "dev-01"
  region = "${var.do_sgp1}"
  size = "s-1vcpu-1gb"
  ssh_keys = [data.digitalocean_ssh_key.ssh_key.id]
}

リージョンや OS は変数用の tf ファイルに書いて参照しています。

# Datacenter regions
variable "do_sgp1" {
  description = "Digital Ocean Singapore Data Center 1"
  default     = "sgp1"
}

# OS
variable "ubuntu" {
  description = "Eoan Ermine"
  default     = "ubuntu-19-10-x64"
}

GitHub Actions のワークフロー定義。PR 作成時に tf ファイルを検証して plan を出力、レビューできるようにします。

各 step で terraform のサブコマンド、init / validate / plan を実行しています。PR を編集するための GitHub アクセストークンと、DigitalOcean の API トークンは Secrets に格納して環境変数に渡しています。

name: Terraform Plan for droplet

on:
  pull_request:
    types: [opened]

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout'
        uses: actions/checkout@master
      - name: 'Terraform Init'
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.15
          tf_actions_subcommand: 'init'
          tf_actions_working_dir: 'droplet'
          tf_actions_comment: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TF_TOKEN }}
          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
      - name: 'Terraform Validate'
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.15
          tf_actions_subcommand: 'validate'
          tf_actions_working_dir: 'droplet'
          tf_actions_comment: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TF_TOKEN }}
          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
      - name: 'Terraform Plan'
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.15
          tf_actions_subcommand: 'plan'
          tf_actions_working_dir: 'droplet'
          tf_actions_comment: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TF_TOKEN }}
          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}

この状態で branch を作成し tf ファイルを更新して PR を作成すると、ワークフローが実行され PR の画面で実行結果をレビューすることができます。

f:id:kondoumh:20191119215754p:plain

plan の結果も表示できます。

f:id:kondoumh:20191119215828p:plain

PR がマージされる時に実行するワークフローです。init の後続でサブコマンド apply を指定しているのと、PR がマージされたかどうか (github.event.pull_request.merged == true) を判定する if を入れています。

name: Terraform Apply droplet

on:
  pull_request:
    types: [closed]

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout'
        uses: actions/checkout@master
      - name: 'Terraform Init'
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.15
          tf_actions_subcommand: 'init'
          tf_actions_working_dir: 'droplet'
          tf_actions_comment: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TF_TOKEN }}
          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
      - name: 'Terraform Apply'
        if: github.event.pull_request.merged == true
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.15
          tf_actions_subcommand: 'apply'
          tf_actions_working_dir: 'droplet'
          tf_actions_comment: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TF_TOKEN }}
          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}

PR をマージするとワークフローが実行されました。

f:id:kondoumh:20191119222408p:plain

そして無事 Droplet が作成されました。

f:id:kondoumh:20191119222440p:plain

Infrastructure as Code に GitHub flow が融合したワークフローですね。

単体の Droplet 作成には羊頭狗肉感は否めませんが、業務で AWS の複雑な依存関係を持つリソースを Terraform で作っているような場合にはかなり役立ちそうです。