2022年に触った技術で年末駆け込み個人開発
はじめに
どうも、貧乏エンジニアリングが趣味なたこくんです。
2022年を振り返ると人生の中でも一大イベント転職がありましたが、
そんなことを振り返るよりも最後まで手を動かしたいということでこんな開発・記事を書きます。(といいつつ振り返り記事も書きたい。)
開発するアプリ
2022年8月ごろに開発したGo言語のバージョン追加をSlack通知するアプリをまた作ります。
インフラまわりをちょこちょこっと変えます。
技術選定
Golang
大好きです。
kubernates
2022年ちょっとずつ触る機会が増えてきました。
cronjobを使ってみたかったので採用します。
okteto
無料でk8sの環境を手に入れることができます。
CockroachDB
無料で5GBのPostgreSQL環境を手に入れることができます。
CockroachDBはGo
で記述された分散SQLデータベースです。
slack api
通知に利用します。
本当はLINEを使ってみたかったですが調べる時間がなかったので断念します。
Terraform Cloud
CockroachDBのデプロイ差分を管理するのに利用します。
が、tfstateをアップロードできなかったので今回は断念しました。
構成図
全体像は以下のような形式となります。
ディレクトリ構成
モノレポで実装します。
.
├── Makefile
├── README.md
├── app // Go実装管理
├── aqua.yaml // 各種ツール管理
├── k8s // manifest管理
└── terraform // infra管理
appディレクトリは以下の構造になります。
おれおれクリーンアーキテクチャです。
app
├── Makefile // 各種コマンド
├── go.mod
├── go.sum
├── cmd
│ ├── migrate // マイグレーションコマンド
│ │ └── main.go
│ └── notice // 通知コマンド
│ └── main.go
└── internal
├── adapter
│ ├── controller // コントローラー層
│ │ └── tag.go
│ ├── gateway // ゲートウェイ層
│ │ ├── github.go
│ │ ├── rdb.go
│ │ └── tag.go
│ └── notifier // 通知層(これはどうなんだろう...)
│ ├── channel.go
│ └── tag.go
├── domain
│ ├── external // 外部依存のインターフェイス
│ │ └── tag.go
│ ├── model
│ │ ├── tag // tagモデルのValue値
│ │ │ ├── name.go
│ │ │ ├── owner.go
│ │ │ └── repo.go
│ │ └── tag.go // tagドメインモデル(IDないので厳密にはドメインではない...)
│ └── repository // 永続層のインターフェイス
│ ├── github.go
│ └── tag.go
├── driver // 外部との接続系
│ ├── config
│ │ └── config.go
│ ├── database
│ │ └── client.go
│ └── slack
│ └── client.go
└── usecase // ユースケース層
├── interactor // ユースケース実装
│ └── tag.go
├── port // ユースケースインターフェイス
│ └── tag.go
└── usecase.go // ユースケース
実装
ロジック自体はなんの工夫もない以下のものとなります。
- GitHubからタグ一覧を取得
- 保存済みのタグ一覧を取得
- 差分を比較
- 差分があった場合は
- 差分を保存
- 差分を通知
package interactor
import (
"context"
"fmt"
"log"
"github.com/takokun778/2022/internal/domain/external"
"github.com/takokun778/2022/internal/domain/model"
"github.com/takokun778/2022/internal/domain/repository"
"github.com/takokun778/2022/internal/usecase/port"
)
type NoticeTag struct {
github repository.GitHub
tagRepository repository.Tag
tagExternal external.Tag
}
var _ port.NoticeTag = (*NoticeTag)(nil)
func NewNoticeTag(
github repository.GitHub,
tagRepository repository.Tag,
tagExternal external.Tag,
) *NoticeTag {
return &NoticeTag{
github: github,
tagRepository: tagRepository,
tagExternal: tagExternal,
}
}
func (nt *NoticeTag) Execute(ctx context.Context, input port.NoticeTagInput) (port.NoticeTagOutput, error) {
dst, err := nt.github.FindAll(ctx, input.Owner, input.Repo)
if err != nil {
return port.NoticeTagOutput{}, fmt.Errorf("failed to list tags: %w", err)
}
src, err := nt.tagRepository.FindAll(ctx, input.Owner, input.Repo)
if err != nil {
return port.NoticeTagOutput{}, fmt.Errorf("failed to list tags: %w", err)
}
diff := model.TakeTags(dst, src)
if len(diff) == 0 {
log.Printf("no tags changed\n")
return port.NoticeTagOutput{}, nil
}
if err := nt.tagRepository.SaveAll(ctx, diff); err != nil {
return port.NoticeTagOutput{}, fmt.Errorf("failed to save tags: %w", err)
}
for _, tag := range diff {
url := fmt.Sprintf("https://github.com/%s/%s/releases/tag/%s", input.Owner, input.Repo, tag.Name)
msg := fmt.Sprintf("released %s\n\n%s", tag.Name, url)
if err := nt.tagExternal.Notice(ctx, msg); err != nil {
return port.NoticeTagOutput{}, fmt.Errorf("failed to notice tag: %w", err)
}
}
return port.NoticeTagOutput{}, nil
}
デプロイ
DockerHubにローカルからプッシュするコマンドは以下となります。
koを好んで使っています。
事前にko login -u ${USERNAME} -p ${PASSWORD} index.docker.io
を実行しておきます。
.PHONY: image
image:
@(cd cmd/notice && ko build --sbom=none --bare --tags=latest ./ --platform=linux/amd64)
kubernates(okteto)へのデプロイするコマンドは以下となります。
事前にoktetoでトークンを発行しokteto login --token ${OKTETO_TOKEN}
を実行しておきます。
DBへの接続情報やSlackのトークンなどは.gitignore対象の.envファイルに記述してenvsubstコマンドでマニフェストに埋め込むようにしています。(これは果たしてセキュアなのでしょうか...)
.PHONY: kapply
kapply:
@envsubst '$$IMAGE','$$DATABASE_URL','$$GITHUB_OWNER','$$GITHUB_REPOSITORY','$$SLACK_TOKEN','$$SLACK_CHANNEL_ID' < k8s/cronjob.yaml > tmp.yaml
@kubectl apply -f tmp.yaml
@rm tmp.yaml
.PHONY: kdelete
kdelete:
@kubectl delete -f k8s/cronjob.yaml
ちゃんと整備するのがめんどくさくなってローカルから実行しました...
GitOpsをちゃちゃっとできるように来年は手札を増やしたいですね。
動作確認
1時間ごとに実行するように以下でmanifestを作成しました。
apiVersion: batch/v1
kind: CronJob
metadata:
name: "2022"
spec:
schedule: "0 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: "2022"
image: ${IMAGE}
imagePullPolicy: IfNotPresent
env:
- name: DATABASE_URL
value: ${DATABASE_URL}
- name: GITHUB_OWNER
value: ${GITHUB_OWNER}
- name: GITHUB_REPOSITORY
value: ${GITHUB_REPOSITORY}
- name: SLACK_TOKEN
value: ${SLACK_TOKEN}
- name: SLACK_CHANNEL_ID
value: ${SLACK_CHANNEL_ID}
restartPolicy: Never
無事に以下のように実行結果がログ出力されました。
2022-12-30 08:00:10.97 UTC2022-27873120-zfnjk20222022/12/30 08:00:10 released go1
2022-12-30 09:00:00.00 UTC2022-27873180-z7xbs[pod-event]Successfully assigned 2022-27873180-z7xbs to gke-cloud-app-xxxx
2022-12-30 09:00:01.00 UTC2022-27873180-z7xbs[pod-event]Pulling image ""
2022-12-30 09:00:06.00 UTC2022-27873180-z7xbs[pod-event]Successfully pulled image "" in 5.018382494s
2022-12-30 09:00:06.00 UTC2022-27873180-z7xbs[pod-event]Created container 2022
2022-12-30 09:00:07.00 UTC2022-27873180-z7xbs[pod-event]Started container 2022
2022-12-30 09:00:09.80 UTC2022-27873180-z7xbs20222022/12/30 09:00:09 no tags changed
おわりに
今回作成したものは以下のリポジトリに置いておきます。
実装してみて
- CICDまわりの手札を増やしたい
- そろそろおれおれクリーンアーキテクチャ用のコード自動生成をしたい
と思いました。
今年何を触ったかなぁを振り返りながらアプリを開発するのも結構楽しかったです。
また来年もやろうと思います。
Discussion