LoginSignup
1
2

More than 1 year has passed since last update.

ローカルTektonでCI/CD(前編)

Last updated at Posted at 2022-02-27

ここで実現したいこと

GitHubにプッシュされたデータを元にイメージをビルドしてDockerHubレジストリに登録する。
レジストリに登録したイメージのタグを元に、Podを更新デプロイする。

GitHubのコミットをトリガーに自動化することもできるが、長くなりすぎるので、別の機会にする。

ここではDeliveryおよびDeployは単純に自分自身のクラスター環境にイメージを展開することにとどめる。
Tektonでデプロイも可能だが、現状、あまり機能が充実してない。ArgoCDなど他のツールに任せるのがよさそう。
DeliveryおよびDeployについても、別の機会で深掘りしたい。

構成イメージ

Tekton(Base).png

minikubeで環境構築

著者の環境

macOS Big Sur v11.6.2
minikube v1.25.1
tkn v0.22.0
k9s v0.25.8

tknは、TektonのCLIツール。動作上、必須ではないが、Tekton上でのステータスやリソースの確認に利用。
https://github.com/tektoncd/cli

k9sも同じく必須ではない。

ローカル環境の構築

minikube起動

minikube start

Docker Desktopのk8sでも可能だが、minikube + hyperkitが端末リソース使用が減らせるみたいな記事を読んだので、今回はこの構成を採る。

TektonでCI/CD実行

準備

Gitリポジトリ

適当に公式nginxイメージを利用したWebサーバーを作ってみた。
デプロイ用にk8sのマニュフェストも作っておく。以下のような構成。

$ tree
.
├── Dockerfile
├── README.md
├── default.conf
├── k8s_manifest
│   └── deploy.yaml
├── nginx.conf
└── src
    └── index.html

マニュフェストの中に、イメージをタグ付きで指定していることが、今回のキモとなる。
コンテンツ更新時、このタグを更新すると、以降のCI/CDのデリバリフェーズにおいて、kubectl applyはテンプレートに変更が入ったと認識して、イメージの差し替えデプロイが発生する。

k8s_manifest / deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: <DockerHubアカウント>/<DockerHubリポジトリ>:<タグ>
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: test-service
  labels:
    app: nginx
spec:
  type: LoadBalancer
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
  selector:
    app: nginx

Tektonのデプロイ

kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

Dockerのクレデンシャルをsecretに登録

ここでは、regcredという名前のsecretに登録する。
後述の手順同様、Yamlファイルにしておいて管理することも可能。その場合は、間違って公開リポジトリに置かないよう注意!

kubectl create secret docker-registry regcred  \
--docker-server=<your-registry-server>  \
--docker-username=<your-name>  \
--docker-password=<your-pword>  \
--docker-email=<your-email>

Tektonリソースファイルの登録

以下のリソースファイルを作成し、登録していく。

  • サービスアカウント
  • クラスターロール
  • クラスターロールバインディング
  • パイプラインリソース
  • タスク
  • パイプライン
  • パイプラインラン

ファイルをまとめて先に作成して、まとめてapplyしてもいいが、Helmやソートプラグイン、あるいはファイル名にプレフィックスを付けるなど、依存関係を意識して順番通りにapplyするようにする必要がある。

サービスアカウントの登録

予め登録しておいたsecretを使って、サービスアカウントを登録する。

sa-docker-registry.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: docker-registry-sa
secrets:
  - name: regcred

デプロイも行えるよう、ロールの登録とバインドもしておく。

clusterrole-deploy.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: deploy-role
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["*"]
- apiGroups: [""]
  resources: ["services"]
  verbs: ["*"]
clusterrolebinding-deploy.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: deploy-role-binding
subjects:
- kind: ServiceAccount
  name: docker-registry-sa
  namespace: default
roleRef:
  kind: ClusterRole
  name: deploy-role
  apiGroup: rbac.authorization.k8s.io

パイプラインリソースの登録

以下のパイプラインリソースを登録する。
<>の中は、自分のリソースを指定。

GitHubリポジトリ

plr-git.yaml
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: git-resource
spec:
  type: git
  params:
    - name: revision
      value: main
    - name: url
      value: https://github.com/<GitHubアカウント>/<GitHubリポジトリ>
plr-image.yaml
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: image-resource
spec:
  type: image
  params:
    - name: url
      value: <DockerHubアカウント>/<DockerHubリポジトリ>

タスクの登録

リソースを利用したタスクを登録しておく。

Gitリポジトリから取得したデータを元に、Dockerイメージをビルド、プッシュするタスク。

イメージのビルドは、Kanikoに委譲する。
以前はTektonタスクでコマンドを実行する場合、commaand:で単発のコマンドを指定する必要があった。
script:では、このようにパイプラインの利用など、よりフレキシブルになった。
Kanikoは執筆現在、通常のイメージではscriptの実行ができなかったため、debugイメージを利用している。

scriptを使って、マニュフェストに記述されたバージョン情報を、DockerHubのタグ情報に指定するようにしている。

task-build-docker-image-from-git.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: build-docker-image-from-git
spec:
  params:
    - name: pathToDockerFile
      type: string
      description: The path to the dockerfile to build
      default: $(resources.inputs.docker-source.path)/Dockerfile
    - name: pathToContext
      type: string
      description: |
        The build context used by Kaniko
        (https://github.com/GoogleContainerTools/kaniko#kaniko-build-contexts)
      default: $(resources.inputs.docker-source.path)
  resources:
    inputs:
      - name: docker-source
        type: git
    outputs:
      - name: builtImage
        type: image
  results:
    - name: manifest-tag
      description: The tag value of manufest.
  steps:
    - name: get-tag
      image: mikefarah/yq:4.20.2
      script: |
        yq ".spec.template.spec.containers[0].image" $(params.pathToContext)/k8s_manifest/deploy.yaml | \
        awk -F':' '{print $2}' | \
        tee $(results.manifest-tag.path)
    - name: build-and-push
#      image: gcr.io/kaniko-project/executor:latest  # for command -> script
      image: gcr.io/kaniko-project/executor:debug
      # specifying DOCKER_CONFIG is required to allow kaniko to detect docker credential
      env:
        - name: "DOCKER_CONFIG"
          value: "/tekton/home/.docker/"
        - name: "IMAGE_DESTINATION"
          value: $(resources.outputs.builtImage.url)
      script: |
        #!/busybox/sh
        set -xe
        IMAGE_TAG=`cat $(results.manifest-tag.path)`
        echo "0: "$IMAGE_TAG
        echo "1: "$IMAGE_DESTINATION
        IMAGE_DESTINATION="${IMAGE_DESTINATION}:${IMAGE_TAG}"
        echo "2: "$IMAGE_DESTINATION
        /kaniko/executor --dockerfile=$(params.pathToDockerFile) \
        --destination=$IMAGE_DESTINATION \
        --context=$(params.pathToContext) \
        --build-arg=BASE=alpine:3

↑でビルドしたイメージをもとに、デプロイするタスク。

task-deploy-using-kubectl.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: deploy-using-kubectl
spec:
  params:
    - name: path
      type: string
      description: Path to the manifest to apply
  resources:
    inputs:
      - name: source
        type: git
      - name: image
        type: image
  steps:
    - name: test-cat-deploy
      image: lachlanevenson/k8s-kubectl
      command: ["cat"]
      args:
        - "$(params.path)"
    - name: run-kubectl
      image: lachlanevenson/k8s-kubectl
      command: ["kubectl"]
      args:
        - "apply"
        - "-f"
        - "$(params.path)"

パイプラインの登録

タスクを呼び出すパイプラインを登録する。

pipeline-build-deploy.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pipeline-build-deploy
spec:
  resources:
    - name: docker-source
      type: git
    - name: builtImage
      type: image
  tasks:
    - name: build-image
      taskRef:
        name: build-docker-image-from-git
      params:
        - name: pathToDockerFile
          value: Dockerfile
        - name: pathToContext
          value: /workspace/docker-source
      resources:
        inputs:
          - name: docker-source
            resource: docker-source
        outputs:
          - name: builtImage
            resource: builtImage
    - name: deploy-image
      taskRef:
        name: deploy-using-kubectl
      resources:
        inputs:
          - name: source
            resource: docker-source
          - name: image
            resource: builtImage
            from:
              - build-image
      params:
        - name: path
          value: /workspace/source/k8s_manifest/deploy.yaml

パイプラインの実行

↑のパイプラインのRunを登録。

pipelineRun-build-deploy.yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: pipeline-build-deploy-run
spec:
  serviceAccountName: docker-registry-sa
  pipelineRef:
    name: pipeline-build-deploy
  resources:
    - name: docker-source
      resourceRef:
        name: git-resource
    - name: builtImage
      resourceRef:
        name: image-resource

ここまでで問題なければ、パイプラインが流れて、テスト用のWebサービスが起動していることが確認できる。
Screenshot at Feb 27 16-51-41.png

実際にCurlでアクセスしてみることも可能。ただし、minikubeの場合、別のターミナルでminikube tunnelをしないと、IPが割り当てられず、pendingのままになる。

トンネル未実施
> kubectl get services
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1       <none>        443/TCP        50m
test-service   LoadBalancer   10.110.42.206   <pending>     80:32711/TCP   30m
トンネル中
> kubectl get services
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1       <none>          443/TCP        56m
test-service   LoadBalancer   10.110.42.206   10.110.42.206   80:32711/TCP   36m
> curl 10.110.42.206

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello World - Nginx Docker</title>
    <style>
        h1{
            font-weight:lighter;
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
</head>
<body>
    
    <h1>
        Hello World! v.001
    </h1>

</body>
</html>

CI/CDのシミュレーション

コンテンツ更新

内容とマニュフェストを更新して、リポジトリ更新する。
こんな感じ。

> git diff
diff --git a/k8s_manifest/deploy.yaml b/k8s_manifest/deploy.yaml
index 22ff6c1..86cc26b 100644
--- a/k8s_manifest/deploy.yaml
+++ b/k8s_manifest/deploy.yaml
@@ -21,7 +21,7 @@ spec:
     spec:
       containers:
       - name: nginx
-        image: pict3/test-web:0.01
+        image: pict3/test-web:0.02
         ports:
         - containerPort: 80
 ---
diff --git a/src/index.html b/src/index.html
index f5b5880..bda38de 100644
--- a/src/index.html
+++ b/src/index.html
@@ -16,7 +16,7 @@
 <body>
     
     <h1>
-        Hello World! v.001
+        Hello World! v.002
     </h1>
 
 </body>

パイプラインを走らせる

今回は、トリガーを使わずに自前で叩く。
一度、PipelineRunを消して再度applyするか、強制リプレイス。

> kubectl replace --force --filename ./pipelineRun-build-deploy.yaml
pipelinerun.tekton.dev "pipeline-build-deploy-run" deleted
pipelinerun.tekton.dev/pipeline-build-deploy-run replaced

更新中

Screenshot at Feb 27 17-11-11.png

再度、サービスへアクセス

コンテンツが更新された(=新しいイメージを元に、Podが置き換えられた)ことが確認できる。

> curl 10.110.42.206

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello World - Nginx Docker</title>
    <style>
        h1{
            font-weight:lighter;
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
</head>
<body>
    
    <h1>
        Hello World! v.002
    </h1>

</body>
</html>

トラブルシューティング

tekton-pipelineが機能しない

tekton-pipelinesネームスペースのPodsのステータスを確認する。
Tektonインストールに失敗している可能性などがある。

パイプラインが動かない

tknコマンドで、Tektonのリソースが正しく作成できているかを確認する。
以下は、適当な順番でapplyしたため、依存関係のあるリソースが未作成のまま、後続をapplyした状態での解析。

> tkn pipelinerun describe pipeline-build-deploy-run -o yaml
...
status:
  completionTime: "2022-02-27T07:06:15Z"
  conditions:
  - lastTransitionTime: "2022-02-27T07:06:15Z"
    message: 'Pipeline default/pipeline-build-deploy can''t be Run; it contains Tasks
      that don''t exist: Couldn''t retrieve Task "build-docker-image-from-git": tasks.tekton.dev
      "build-docker-image-from-git" not found'
    reason: CouldntGetTask
    status: "False"
...

パイプラインは動いたが、エラーが発生

Podのログを確認する。Step単位で確認可能。

まとめ

そこまで難易度が高くなく、CI/CD環境が構築できた。(CI/CDというには少し弱いが)

ここで実装した内容について、DockerHubのAutomated Buildsを使えばいいのでは?とか、AWSのCode BuildやGoogle CloudのCloud Buildに寄せればいいんじゃなかろうかという意見もあるかもしれない。たしかに、特定環境で特殊な要件がなければ、CI/CD環境構築の手間を割愛できるマネージドサービスを選択した方がいいことも多い。

ただ、自前の環境は自由度が高いことと、一度はこのように自前で構築してみることで、マネージドサービスに対する理解も上がる気がしている。

リポジトリ

とくに需要はないかもだが、一応、以下に公開しておく。
パイプラインリソースなどは適当に変えて利用ください。
Tekton環境:
https://github.com/pict3/my-tekton
サンプルWeb:
https://github.com/pict3/test-docker-nginx

参考・参照

1
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2