【Day 25】Skaffoldで起動する

## はじめに

![スライド26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/3cac80a9-5c52-5594-c42a-9a39889b3136.png)

---

**2023年アドベントカレンダー最終日**です。

https://qiita.com/advent-calendar/2023/life-sync-app

昨日と今日で、k8sの環境をskaffoldで作っています。

本日は立ち上げの最後までいきます。

## 各Dockerfileの作成

各サービスを立ち上げるためのDockerfileを作成します。

ディレクトリ構造のイメージとしてはこのような感じです

```txt
.
├── api
├── bff
├── dockers
|   ├── api
|   |   └── Dockerfile
|   ├── bff
|   |   └── Dockerfile
|   ├── db
|   |   ├── init.sql
|   |   └── Dockerfile
|   └── web
|       └── Dockerfile
└── web
```

### Web

WebはNext.jsで動きます。

`nodejs`のイメージを使い、`build`してから、`start`するような形が良いでしょう

```Dockerfile:dockers/web/Dockerfile
FROM node:21-alpine3.17

WORKDIR /life-sync-web

COPY ./web /life-sync-web

RUN npm install && npm run build

CMD ["npm", "run", "start"]
```

### BFF

BFFはNestJSで動きます。

`nodejs`のイメージを使い、`prod`モードで起動するように`start:prod`しようと思います

```Dockerfile:dockers/bff/Dockerfile
FROM node:21-alpine3.17

WORKDIR /life-sync-bff

COPY ./bff /life-sync-bff

RUN npm install

CMD ["npm", "run", "start:prod"]
```

### API

APIはRustで動きます。

`nodejs`のイメージを使い、`cargo run`します。

```Dockerfile:dockers/api/Dockerfile
FROM rust:1.73-buster

WORKDIR /life-sync-api

COPY ./api /life-sync-api

CMD ["cargo", "run"]
```

### DB

DBはpostgresqlで動かします。
起動後に、`init.sql`を動かしたいので、Dockerfileを作っていきます

```sql:dockers/db/init.sql
CREATE TABLE blog (
    id UUID PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    author VARCHAR(255) NOT NULL,
    body TEXT NOT NULL,
    created_at VARCHAR(255) NOT NULL
);
```

```Dockerfile:dockers/db/Dockerfile
FROM postgres:16

COPY ./dockers/db/init.sql /docker-entrypoint-initdb.d/
```

## Skaffold

Skaffoldとは、Googleによって開発されたKubernates開発のためのコマンドラインツールです。

Skaffoldについては私が所属するUzabaseの先輩が書いた素晴らしい記事があるので、こちらをお読みください

https://tech.uzabase.com/entry/2023/10/26/155559

### Skaffold.yaml

Skaffold.yamlを作成しました。


```yaml:skaffold.yaml
apiVersion: skaffold/v2beta19
kind: Config
build:
  artifacts:
    - image: life-sync-web
      context: .
      docker:
        dockerfile: ./dockers/web/Dockerfile
    - image: life-sync-bff
      context: .
      docker:
        dockerfile: ./dockers/bff/Dockerfile
    - image: life-sync-api
      context: .
      docker:
        dockerfile: ./dockers/api/Dockerfile
    - image: life-sync-db
      context: .
      docker:
        dockerfile: ./dockers/db/Dockerfile
deploy:
  kubectl:
    manifests:
      - k8s/web/*.yaml
      - k8s/bff/*.yaml
      - k8s/api/*.yaml
      - k8s/db/*.yaml
portForward:
  - resourceType: service
    resourceName: life-sync-web
    namespace: default
    port: 3000
    localPort: 3000
  - resourceType: service
    resourceName: life-sync-bff
    namespace: default
    port: 3000
    localPort: 4000
  - resourceType: service
    resourceName: life-sync-api
    namespace: default
    port: 3000
    localPort: 8000
  - resourceType: service
    resourceName: life-sync-db
    namespace: default
    port: 5432
    localPort: 5432
```

上から順に、

1. DockerImageを指定して
1. deployのためのmanifestファイルの場所を指定して
1. portを開放する

といった感じです

## manifestファイル

```
.
├── api
├── bff
├── dockers
├── k8s
|   ├── api
|   |   ├── deployment.yaml
|   |   └── service.yaml
|   ├── bff
|   |   ├── deployment.yaml
|   |   └── service.yaml
|   ├── db
|   |   ├── service.yaml
|   |   └── statefulset.yaml
|   └── web
|   |   ├── deployment.yaml
|   |   └── service.yaml
└── web
```

### Web

<details><summary>deployment.yaml</summary>

```yaml:k8s/web/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: life-sync-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: life-sync-web
  template:
    metadata:
      labels:
        app: life-sync-web
    spec:
      containers:
        - name: life-sync-web
          image: life-sync-web:latest
          ports:
            - containerPort: 3000
```

</details>

<details><summary>service.yaml</summary>

```yaml:k8s/web/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: life-sync-web
spec:
  selector:
    app: life-sync-web
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 3000
  type: LoadBalancer
```

</details>

### BFF

<details><summary>deployment.yaml</summary>

```yaml:k8s/bff/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: life-sync-bff
spec:
  replicas: 1
  selector:
    matchLabels:
      app: life-sync-bff
  template:
    metadata:
      labels:
        app: life-sync-bff
    spec:
      containers:
        - name: life-sync-bff
          image: life-sync-bff:latest
          ports:
            - containerPort: 3000
```

</details>

<details><summary>service.yaml</summary>

```yaml:k8s/bff/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: life-sync-bff
spec:
  selector:
    app: life-sync-bff
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 3000
  type: LoadBalancer
```

</details>

### API

<details><summary>deployment.yaml</summary>

```yaml:k8s/api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: life-sync-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: life-sync-api
  template:
    metadata:
      labels:
        app: life-sync-api
    spec:
      containers:
        - name: life-sync-api
          image: life-sync-api:latest
          ports:
            - containerPort: 3000
```

</details>

<details><summary>service.yaml</summary>

```yaml:k8s/api/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: life-sync-api
spec:
  selector:
    app: life-sync-api
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 3000
  type: LoadBalancer
```

</details>

### DB

<details><summary>service.yaml</summary>

```yaml:k8s/db/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: life-sync-db
spec:
  ports:
  - protocol: TCP
    port: 5432
    targetPort: 5432
  selector:
    app: life-sync-db
  type: ClusterIP
```

</details>

<details><summary>statefulset.yaml</summary>

```yaml:k8s/db/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: life-sync-db
spec:
  serviceName: life-sync-db
  replicas: 1
  selector:
    matchLabels:
      app: life-sync-db
  template:
    metadata:
      labels:
        app: life-sync-db
    spec:
      containers:
        - name: life-sync-db
          image: life-sync-db:latest
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_DB
              value: life-sync
            - name: POSTGRES_USER
              value: postgres_user
            - name: POSTGRES_PASSWORD
              value: postgres_password
```

</details>


## 起動

これで起動してみます！


:::note warn
今回の設定とは別で各サービスで他サービスを指定している個所をENVに移行したり、CORSの設定をしたりしました。本題とはずれるので省略しました。
:::

```bash
skaffold dev
```

起動ができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/3b4dfc5f-aea4-8f31-aab7-e5d28cf70f30.png)

直接DBに入り、データをいくつか入れてみました。

:::note info
データ投入方法

`psql`を利用して、直接アクセスしました。

```bash
psql -h localhost -p 5432 -U postgres_user -W -d life-sync
```

好みに合わせて、A5M2や、DBeaverなどのGUIツールを入れてもいいでしょう
:::

ちゃんと表示されました👍

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/bca8a849-d548-e3e8-3cc1-7fb1f3d26a65.png)

## 最後に・アドベントカレンダーを振り返って

去年は一貫性の無いアドベントカレンダーを走りましたが、今年は一つのアプリを構築してみました。
そのため、**ユーザーストーリー**、**デザイン**、**Webの実装**、**BFFの実装**、**APIの実装**、**各種テスト・モック**、**K8s環境構築**といったWebアプリケーションを作る上で必要な一通りの実装を触ることができました。

（実際にクラウド上にデプロイするとなると、もう少しネットワーク関係やセキュリティ面でやることはあるでしょう）

25個の記事を書くのは大変でしたが、楽しかったです

来年も頑張るぞ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/7b1c8110-9442-8d59-2353-ff194929e926.png)