GitHub ActionsでCI/CDパイプラインを構築する
GitHub Actionsを使ったGoアプリのCI(テスト・lint)とCD(サーバーへのデプロイ)パイプラインを、実際のワークフローファイルで解説します。
GitHub Actionsとは
GitHub ActionsはGitHubに組み込まれたCI/CDサービスです。リポジトリへのpushやPull Requestをトリガーに、テスト・ビルド・デプロイまでを自動化できます。
本記事ではGoのWebアプリを例に、以下の2つのワークフローを構築します。
- CIワークフロー:PRのたびにlintとテストを実行
- CDワークフロー:mainブランチへのマージ後にサーバーへデプロイ
ディレクトリ構成
.github/
└── workflows/
├── ci.yml
└── deploy.yml
CIワークフロー(ci.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
| name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
- name: Test
run: go test ./... -race -coverprofile=coverage.txt
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: coverage.txt
|
go test -race は競合状態(Race Condition)を検出するオプションです。並行処理を含むGoアプリでは常に付けておくことをすすめます。
CDワークフロー(deploy.yml)
mainブランチにマージされたときだけ動かし、SSH経由でサーバーにデプロイします。
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
| name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
needs: [] # CI が別ワークフローの場合はここで依存を定義
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Build
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -o dist/server .
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
systemctl stop myapp || true
mkdir -p /opt/myapp
- name: Copy binary
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
source: dist/server
target: /opt/myapp/
- name: Start service
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
systemctl start myapp
systemctl status myapp
|
Secretsの設定
デプロイに必要な認証情報はGitHubの Settings → Secrets and variables → Actions で登録します。
| Secret名 | 内容 |
|---|
SSH_HOST | サーバーのIPアドレスまたはホスト名 |
SSH_USER | SSHユーザー名 |
SSH_PRIVATE_KEY | SSHの秘密鍵(~/.ssh/id_ed25519 の内容) |
SSH_PORT | SSHポート番号 |
秘密鍵をベタ書きしてコードに混入させないよう、Secretsに登録して参照するのが鉄則です。
systemdのサービス定義
サーバー上にsystemdのunit fileを配置しておきます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # /etc/systemd/system/myapp.service
[Unit]
Description=My Go Application
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/server
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
|
1
2
| sudo systemctl daemon-reload
sudo systemctl enable myapp
|
ジョブの依存関係
CIとCDを1つのワークフローに書く場合は needs でジョブの順序を制御します。
1
2
3
4
5
6
7
8
9
10
| jobs:
test:
runs-on: ubuntu-latest
# ...
deploy:
runs-on: ubuntu-latest
needs: test # test が成功したときだけ実行
if: github.ref == 'refs/heads/main'
# ...
|
まとめ
- CIはPRのたびに走らせてコード品質の劣化を防ぐ
- CDはmainブランチへのマージをトリガーにする
- 認証情報はすべてGitHub Secretsに入れる
needs でジョブの依存関係を明示し、テスト失敗時のデプロイを防ぐ
GitHub Actionsは無料枠(月2,000分)があるため、個人プロジェクトや小規模チームであれば追加コストなしで使えます。