コンテンツにスキップ

[5/5] GiteaでCI/CDパイプラインを構築する デプロイ編

はじめに

この記事はGiteaを使ったCI/CDパイプラインの構築方法を展開する連載の一つである。

連載の各記事へは以下からジャンプできる。

  1. Giteaインストール編
  2. アプリケーション構築編
  3. テスト編
  4. ビルド編
  5. デプロイ編

CI/CDパイプラインでデプロイする

この記事では以下の赤枠部である、デプロイの自動実行をやってみよう。

アプリケーションのデプロイ方法は多種多様である。クラウド環境にデプロイするかもしれないし、オンプレミス環境にデプロイするかもしれない。

今回は例としてSSHを使ってオンプレミス環境のサーバーにデプロイする想定で進めるが、デプロイ手順がCLIで完結するなら今回紹介する方法の応用で環境を問わずデプロイを自動化できる。

Workflowにデプロイのジョブを追加する

.gitea/workflows/workflow.ymldeployのジョブを追加する。

このジョブはサーバーへSSHで接続し、Dockerイメージをコンテナとして実行する。

.gitea/workflows/workflow.yml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
name: Workflow
on:
  push:
    branches:
      - main # mainブランチに対して実行する
env:
  NODE_VERSION: "24.14.1" # 使用するNode.jsのバージョンを定義する
jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v5
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - name: Install dependencies
        run: npm ci
      - name: Run typecheck
        run: npm run typecheck
  test:
    needs: typecheck # typecheckが通ったら実行する
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v5
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - name: Install dependencies
        run: npm ci
      - name: Run test
        run: npm run test
  build:
    runs-on: ubuntu-latest
    needs: [typecheck, test] # typecheckとtestが通ったら実行する
    steps:
      - name: Checkout
        uses: actions/checkout@v5
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      # Docker BuildxにHTTPのコンテナレジストリへのpushを許可する
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          buildkitd-config-inline: |
            [registry."${{ vars.REGISTRY_ENDPOINT }}"]
              http = true
              insecure = true
      # コンテナレジストリへの認証情報を設定する
      - name: Log in to Gitea container registry
        run: |
          mkdir -p ~/.docker
          AUTH=$(echo -n "${{ gitea.actor }}:${{ secrets.PERSONAL_ACCESS_TOKEN }}" | base64 -w 0)
          echo "{\"auths\":{\"${{ vars.REGISTRY_ENDPOINT }}\":{\"auth\":\"$AUTH\"}}}" > ~/.docker/config.json
      - name: Build and push
        run: |
          IMAGE_NAME=$(node -p "require('./package.json').name")
          TAG_NAME=$(node -p "require('./package.json').version")
          docker buildx build \
            --platform linux/amd64,linux/arm64 \
            --tag ${{ vars.REGISTRY_ENDPOINT }}/${{ gitea.repository_owner }}/$IMAGE_NAME:$TAG_NAME \
            --tag ${{ vars.REGISTRY_ENDPOINT }}/${{ gitea.repository_owner }}/$IMAGE_NAME:latest \
            --cache-from type=registry,ref=${{ vars.REGISTRY_ENDPOINT }}/${{ gitea.repository_owner }}/$IMAGE_NAME:buildcache \
            --cache-to type=registry,ref=${{ vars.REGISTRY_ENDPOINT }}/${{ gitea.repository_owner }}/$IMAGE_NAME:buildcache,mode=max \
            --provenance=false \
            --push \
            .
  deploy:
    runs-on: ubuntu-latest
    needs: [typecheck, test, build]
    steps:
      - name: Checkout
        uses: actions/checkout@v5
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      - name: Prepare SSH Key
        run: |
          # ジョブ実行用のコンテナにSSHの設定をしてやる必要がある
          mkdir ~/.ssh && chmod 0700 ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 0400 ~/.ssh/id_rsa
          ssh-keyscan -p22 -H ${{ vars.DEPLOYMENT_HOST }} > ~/.ssh/known_hosts
      - name: Deploy
        run: |
          IMAGE_NAME=$(node -p "require('./package.json').name")
          ssh ${{ vars.DEPLOYMENT_USER }}@${{ vars.DEPLOYMENT_HOST }} "
            docker pull ${{ vars.REGISTRY_ENDPOINT }}/${{ gitea.repository_owner }}/$IMAGE_NAME:latest &&
            docker stop $IMAGE_NAME;
            docker rm $IMAGE_NAME;
            docker run -d --name $IMAGE_NAME -p 3030:3000 ${{ vars.REGISTRY_ENDPOINT }}/${{ gitea.repository_owner }}/$IMAGE_NAME:latest
          "

deployのジョブでは以下のことを行っている。

  • リポジトリをチェックアウトする
  • Node.js環境を用意する
  • SSHの設定を行う
  • デプロイ先へSSH接続し、Dockerイメージのプルを行ったあとコンテナとして実行する

リポジトリのSettingsにあるActions-Variablesからデプロイ先サーバーのIPアドレスとユーザー名を追加する。これはSSHの接続先になる。

リポジトリのSettingsにあるActions-Secretsからデプロイ先サーバーのSSHの秘密鍵を登録する。(秘密鍵の内容は別途デプロイ先サーバーにSSH接続し、cat ~/.ssh/id_rsaなどを実行することで確認できる)

末尾には改行を入れること。これをしないと接続に失敗する。

デプロイ先サーバーからGiteaに登録されたDockerイメージを参照できるようにする

前回の記事に書いたとおり、Dockerはデフォルトの設定ではHTTPのレジストリと通信しようとするとエラーになる。これはデプロイ先サーバーでも同様である。

デプロイ先サーバーの/etc/docker/daemon.jsonに以下の設定をマージする。

/etc/docker/daemon.json
1
2
3
{
  "insecure-registries": ["xxx.xxx.xxx.xxx:yyyy"]
}

Dockerデーモンを再起動する。

1
sudo systemctl restart docker.service docker.socket

GiteaへプッシュしたときCI/CDパイプラインが実行されることを確認する。

Webブラウザでhttp://${デプロイ先サーバーのIPアドレス}:3030にアクセスしたとき、1+1の結果が返ってきたらデプロイに成功している。

仕様変更に対応する

ここまで長い道のりを辿ってCI/CDパイプラインを構築してきた。

機能追加や仕様変更でその効果を実感することができる。

足し算を1+1から1+2に変更しよう。

src/app.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { Hono } from "hono";
import { sum } from "./sum.js";

export const app = new Hono();

/**
 * 足し算API
 */
app.get("/sum", (c) => {
  return c.json({ result: sum(1, 2) });
});

バージョンも上げておこう。

package.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "name": "cicd-example",
  "type": "module",
  "version": "1.1.0",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "typecheck": "tsc --noemit",
    "test": "vitest"
  },
  "dependencies": {
    "@hono/node-server": "^1.19.14",
    "hono": "^4.12.17"
  },
  "devDependencies": {
    "@types/node": "^20.11.17",
    "tsx": "^4.7.1",
    "typescript": "^5.8.3",
    "vitest": "^4.1.5"
  }
}

リポジトリへプッシュする。

1
2
3
$ git add -A
$ git commit -m "足し算を変更した"
$ git push

CI/CDパイプラインが自動実行される。内容を確認すると、テストで失敗していることがわかる。

テストのジョブを詳しく見るとAPIの戻り値が期待値と異なることがわかる。テストの期待値の更新を忘れていたようだ。

テストが失敗しているのでDockerイメージのビルドやデプロイは行われていない。

テストコードの期待値を更新する。

src/app.test.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { describe, expect, test } from "vitest";
import { app } from "./app.js";

describe("足し算API", () => {
  test("正しい足し算の結果を得られること", async () => {
    const response = await app.request("/sum");

    // HTTPステータスコードが200であること
    expect(response.status).toBe(200);

    // 足し算の結果が期待値と一致すること
    const body = await response.json();
    expect(body).toEqual({ result: 3 });
  });
});

再度リポジトリへプッシュする。

1
2
3
$ git add src/app.test.ts
$ git commit -m "足し算のテストコードを見直した"
$ git push

CI/CDパイプラインが自動実行される。今度はテストが通り、ビルドもデプロイも成功する。

Webブラウザでhttp://${デプロイ先サーバーのIPアドレス}:3030にアクセスしたとき、1+2の結果が返ってくることから、最新のアプリケーションがデプロイされていることがわかる。

振り返り

  • Gitea ActionsのCI/CDパイプラインでアプリケーションをデプロイすることができた

リポジトリ

最終形のリポジトリを以下に展開する。

GitHub - kkawaharanet/cicd-example · GitHub

おわりに

Giteaを使ったCI/CDパイプラインを構築し、テスト・ビルド・デプロイを自動化することができた。

今回の記事では題材にTypeScriptのWebアプリケーションを選んだが、学んだことを応用すればPythonでの開発であってもビルドがWindows向けのバイナリであってもCI/CDパイプラインを構築できる。

自動化によって浮いた時間は開発にも使えるし、別分野の学習にも使える。素晴らしいことだ。