えんじにあメモ

試してみた技術とか、たまに家電ネタ

GitHub Actionsを使ってラズパイk8sクラスタの自動デプロイ環境を作る

最近Netflixを登録し、ジョジョを一気観しました
6部のアニメ化も期待しています

はじめに

以前作成した ラズパイのk8sクラスタ 上にアプリケーションをデプロイする際に、今までは同じネットワークにあるローカルMacから直接kubectlやhelmコマンドでデプロイを行ってました

利用するDockerイメージのアップロードはGitHub Actionsを利用してgit push → DockerHubへのアップロードを自動化していましたが、今回はk8sへのデプロイ部分もGitOpsで自動化していきたいと思います 💪

GitHub Actionsを利用してラズパイ(複数アーキテクチャ)向けのイメージを作る方法は↓こちら

sminamot-dev.hatenablog.com

TL;DR

  • GitHub Actionsのセルフホストランナーを利用する
  • 構築パターンは以下の2つ
    • ラズパイ上に直接構築する
    • 専用のイメージを作成し、k8sのPodとして構築する
  • 作成した専用イメージ
  • 必要なツールをラズパイ上もしくはイメージに含め、GitHub Actionsのジョブ上で利用してデプロイを行う

GitHub Actionsのセルフホストランナー

セルフホストランナーとは独自の環境(VMなど)をGitHub Actionsのジョブ実行環境にすることができる機能です

docs.github.com

ポーリングによってGitHubとの通信を行うため、自分のラズパイのように外部に穴あけされていない環境でも利用することができます

セルフホストランナーを利用する上での注意点として、パブリックリポジトリで利用しないことが推奨されています

パブリックリポジトリのフォークでは、ワークフロー中のコードを実行するプルリクエストが作成されると、セルフホストランナー上で危険なコードが実行される可能性があります。

今回はセルフホストランナーを構築する環境として、k8sクラスタとして利用しているラズパイ上に直接設定を行うパターンと、k8s上に専用のPodを立てて利用するパターンの2パターンを試してみます

ラズパイ上に直接設定を行う

まずワークフローを設定するためのプライベートリポジトリを作成し、「Settings」→「Actions」を開きます f:id:shosfs:20200706192206p:plain

「Add Runner」を押し、OSとArchitectureを選びます(ラズパイの場合はOS:Linux、Architecture:ARMを選択) f:id:shosfs:20200706192510p:plain

セフルホストランナーとして利用するラズパイにログインして表示されているコマンドを実行します

# Download
$ mkdir actions-runner && cd actions-runner
$ curl -O -L https://github.com/actions/runner/releases/download/v2.267.1/actions-runner-linux-arm-2.267.1.tar.gz
$ tar xzf ./actions-runner-linux-arm-2.267.1.tar.gz

# Configure
$ ./config.sh --url https://github.com/sminamot/<作ったprivateリポジトリ> --token XXXX
$ ./run.sh

config.shの実行時にrunnerの名前、追加のlabel、workフォルダを聞かれますが、ひとまずすべてデフォルトのままEnter

run.shを実行し、Listening for Jobs と表示され、GitHubのActions画面でセルフホストランナーが追加されていればOKです f:id:shosfs:20200706193450p:plain

あとはいつものようにGitHub Actionsのワークフロー設定を追加すればOKですが、yaml中のruns-onにはself-hostedを指定するようにしてください

以下は作成したプライベートリポジトリへpushした際に、ラズパイk8sクラスタ上にbusyboxイメージでsleepするだけのpodを作るワークフロー例です

name: Deploy

on:
  push:

jobs:
  deploy:
    runs-on: self-hosted

    steps:
    - name: deploy
      run: kubectl run busybox --image busybox --restart Never -- /bin/sh -c "sleep 3600"

デプロイするための事前準備として、対象のラズパイ上でkubectlが実行できるようにしておく必要があります

上記yaml.github/workflows/k8s.ymlとして保存しgit pushを行うとデプロイが走りbusyboxのPodがデプロイされました

GitHub上のAction画面

f:id:shosfs:20200706200012p:plain

Pod確認

f:id:shosfs:20200706195945p:plain

run.shと同じディレクトリにあるsvc.shを使うことでランナーをサービス化することができます

# 事前にrun.shを動かしている場合は終了しておく

# インストール
$ ./svc.sh install

# 起動
$ ./svc.sh start

# ステータス確認
$ ./svc.sh status

# 停止
$ ./svc.sh stop

# アンインストール
$ ./svc.sh uninstall

実際のデプロイフローとしては以下のようになります

  • ラズパイ上にデプロイに必要なツールを事前にインストール(kustomizeやhelmなど)
  • 作成したプライベートリポジトリ上にデプロイしたいk8sのリソース用yamlを配置し、↑のツールを利用してgit push時にデプロイを行う

k8s上に専用のPodを立てる

上記のランナーをk8sのPod上に立てる方法です

セルフホストランナー用ベースイメージ

Podとして動かすために以下のようなDockerfileとentrypoint.sh/remove.shを作成します

github.com

作成したイメージ

Dockerfileはセルフホストランナーのためのベース用イメージであり、実際に使うイメージではこちらをベースにデプロイに必要なツール等を含めたイメージにします(後述)

また、amd64/armともにbuildできるよう環境別にARGを設定し、FROM base-$TARGETARCHの記述で各Archに対応できるようにしています

「はじめに」で紹介した複数アーキテクチャ向けのイメージ作成の方法ではbuildxを使っているため、$TARGETARCHにそれぞれのArchの値が入ることで処理の分岐が行われます

(再掲) sminamot-dev.hatenablog.com

entrypoint.shとremove.shを分けているのは、ジョブ実行後以外にPod終了時にもRunnerの解除を行いたいためです

entrypoint.shではrun.shの実行時に--onceオプションをつけてジョブを1つ実行後に終了するようにしています
これによりデプロイ時に生成したファイル等がPod内に残らないようにしています

k8s用イメージ

上記Dockerfileをベースにして実際にk8sクラスタ上に配置するイメージを作成します
自分はデプロイにkubectl、kustomize、sopsを利用しているため、これらのバイナリを追加したイメージにしています

github.com

作成したイメージ

kustomizeとsopsはarm向けのバイナリが公式に提供されていなかったため、docker build時にソースからビルドする手順としています

イメージが用意できたら実際にPod(Deployment)をラズパイk8sクラスタ上にデプロイします

Pod上でkubectlを実行するための、ServiceAccountClusterRoleBindingも一緒に作成します

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: github-actions-runner-k8s
  labels:
    app: github-actions-runner-k8s
spec:
  replicas: 2
  selector:
    matchLabels:
      app: github-actions-runner-k8s
  template:
    metadata:
      labels:
        app: github-actions-runner-k8s
    spec:
      serviceAccount: admin
      containers:
      - name: github-runner
        image: sminamot/github-actions-runner-k8s:1.0.0
        imagePullPolicy: IfNotPresent
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "./remove.sh"]
        envFrom:
        - configMapRef:
            name: github-actions-configmap
        - secretRef:
            name: github-actions-secret

↑で書いたとおりpreStopにランナーを外す処理を入れています

serviceaccount.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin

clusterrolebinding.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-clusterrolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin
  namespace: default

deployment.yaml中のconfigMapとsecretには以下の環境変数を設定するための値を指定しておきます

key value configMap/secret
GITHUB_OWNER 対象リポジトリのOwner(ORG) configMap
GITHUB_REPOSITORY 対象のリポジトリ configMap
GITHUB_TOKEN GitHubアクセストークン(repoの権限を付与したもの) secret

以上のリソースをすべて作成し、ログを確認すると(sternで確認)、ラズパイ上で直接動かしたときと同様に Listening for Jobs と出力されランナーが起動しています f:id:shosfs:20200726133054p:plain

対象リポジトリのActionsの設定を見ると、自動的にランナーが追加されていることが分かります f:id:shosfs:20200726133632p:plain

先程のentrypoint.shでは終了時のremove処理も書かれているので、Podが終了した際に自動的にランナーから削除が行われます

あとは同様に対象のプライベートリポジトリ上にデプロイ用のワークフロー設定を追加すればOKです

終わりに

GitHub Actionsを利用したGitOpsでの自動デプロイ環境の構築方法について紹介しました

2つやり方を紹介しましたが、自分はPodを立てる方のやり方で利用しています

GitHub Actionsでは指定したパス以下のファイルが更新されたときだけ動く設定があるため、k8sデプロイ用のプライベートリポジトリにアプリケーションごとにyamlを配置するディレクトリを作成し、それぞれ更新時のみデプロイされるように設定しました

kustomizeを使った例だと↓のような感じです
ファイル構成

.
├── .github
│   └── workflows
│       ├── my-app1.yaml
│       └── my-app2.yaml
├── my-app1
│   ├── deployment.yaml
│   └── kustomization.yaml
└── my-app2
    ├── deployment.yaml
    └── kustomization.yaml

.github/workflows/my-app1.yaml

name: Deploy my-app1

on:
  push:
    paths:
      - 'my-app1/**'
      - '.github/workflows/my-app1.yaml'

jobs:
  deploy:
    runs-on: self-hosted

    steps:
    - uses: actions/checkout@v2

    - name: Run kubectl apply
      run: kustomize build my-app1 | kubectl apply -f -

Let's 自動デプロイライフ🚀

参考