Open5

何もわかってない初心者がGolang+Nuxt3+Nginxなdocker環境を構築する奮闘記

AnonymousNinjaAnonymousNinja

次の現場が「Golang」+「Nuxt3」な環境での開発を行っているらしいので、勉強がてら自分でローカル環境を作成してみようと思い立った。

◎簡単なプロフィール◎

  • エンジニア2年生
  • 未経験からの転職組
  • これまではPHP7+CodeIgniterのレガシースタックを使ってきた
  • Apacheしか使ったことがない

次への一歩として新たな環境にチャレンジ中。
奮闘記としてスクラップ程度で調べたことや理解したこと・整理したことを残しておく。

AnonymousNinjaAnonymousNinja

■今回作成するもの

  • 簡単な管理ツール(なにかの。作りながら決める。)
  • LINEのAPIにちょっと興味があるので、LINEアプリとの連携を目指す。

■今回のシステム構造

  • あくまでも願望
  • できるかどうかは調べてない
  • 実装段階なので後から追記する

▼2024年9月20日時点(最終更新:2024年9月22日)

  • APIサーバー
    • Go + echo
    • バージョン
      • GO:1.23.1
      • echo:
  • フロント
    • Vue3 + Nuxt3
    • バージョン
      • Node.js:20.17.0
      • Vue3:3.5.6
      • Nuxt3:3.13.0
      • pnpm:
  • Proxyサーバー
    • Nginx
    • バージョン:latest
  • データベース
    • MySQL
    • バージョン:9.0.1
AnonymousNinjaAnonymousNinja

①各コンテナのディレクトリ・ファイルを整備

最終的には添付画像のような形になった。


▼ディレクトリ・ファイルについての詳細

【apiディレクトリ】

APIサーバー用コンテナを作成。

  • .air.toml
    • Golangで形成するAPIサーバーで、ホットリロードを有効化するために必要なモジュール(Air)の設定ファイル(?)
    • 基本的にはモジュール公式GitHubair_example.tomlをそのまま引用
      • 後で一部環境用に書き換えるかも?
      • 色々参考にした記事の中ではそのままでも使えるというのはよく見かけた
  • Dockerfile
api/Dockerfile

# Install Golang @2024-09-18
FROM golang:1.23.1-alpine

ENV ROOT=/app/src/GolangNuxt/backend
ENV TZ=Asia/Tokyo

WORKDIR ${ROOT}

## Enable Go modules.
ENV GO111MODULE=on

## Enable hot reload.
RUN go install github.com/air-verse/air@latest

COPY ./.air.toml ${ROOT}/.air.toml

RUN apk update && \
    apk upgrade && \
    apk add --no-cache \
            git gcc bash vim \
            make which less musl-dev

EXPOSE 8080 5173

CMD air

【clientディレクトリ】

Nuxt3用コンテナを作成

  • Dockerfile
client/Dockerfile
FROM amazonlinux:2023.5.20240903.0

RUN ln -snf /usr/share/zoneinfo/Japan /etc/localtime

## Install basic tools.
RUN dnf -y install \
        git gcc bash vim tar \
        which less

# Install Node.js LTS version. @2024-09-19
ENV NODE_VERSION 20.17.0

## Install nvm.
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

RUN . $HOME/.nvm/nvm.sh && \
    nvm install ${NODE_VERSION} && \
    nvm use ${NODE_VERSION} && \
    npm install -g pnpm && \
    node -v && npm -v

### nvmの初期化するように.bashrcに書き込む.
RUN echo -e "\
export NVM_DIR=\"\$HOME/.nvm\"\n\
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"\n\
" >> ~/.bashrc

EXPOSE 3000

【databaseディレクトリ】

MySQL用コンテナを作成

  • data
    • データ永続化用のディレクトリ
    • デフォルトでは.gitkeepのみがある状態
  • Dockerfile
database/Dockerfile
# Install MySQL @2024-09-20
FROM mysql:9.0.0

## timezone.
RUN ln -snf /usr/share/zoneinfo/Japan /etc/localtime

COPY ./my.cnf /etc/my.cnf

  • my.cnf
    • MySQL設定用ファイル
    • (ファイル名は違うかもだけど)探したら割と落ちてるので参考にした

【serverディレクトリ】

Nginx用コンテナを作成

  • log
    • ログファイルsync用
    • デフォルトでは.gitkeepのみがある状態
    • access_logerror_logをバインド
  • Dockerfile
server/Dockerfile
FROM nginx:alpine

# Remove the default configuration file.
RUN rm -f /etc/nginx/conf.d/*

COPY ./nginx.conf /etc/nginx/conf.d/default.conf

# TimeZone.
RUN apk add --no-cache tzdata
ENV TZ Asia/Tokyo

# Enable the Nginx service After build.
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

  • nginx.conf
nginx.confの中身
server_tokens off;

server {
    # Listen on port 80
    listen 80;
    # server_name is the domain name of the server
    server_name www.nuxt-golang.jp;

    # Proxy / requests to the client server
    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_pass http://client:3000/app/src/public/;
    }

    # Proxy /api requests to the API server
    location /api/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_pass http://api:8080/;
    }
}
AnonymousNinjaAnonymousNinja

②コンテナのビルド

コンテナをビルドする前に、app/src配下にプロジェクトのソースコードをクローン

# app/srcは、デフォルトで.gitignoreでignoreされており、.gitkeepのみが存在する状態
cd app/src
git clone -b develop 「GitHubプロジェクトのURL」

cd ../../docker
docker compose up -d --build

エラーが出た箇所については適宜修正
※ほぼエラーは出なかったので恐らく大丈夫

コンテナが永続化できない場合

個人的に悩まされたのはmysqlのコンテナがExit(1)になってしまう現象。
これは現職でも一回悩まされたのを後日思い出したので無事解決。

以下解決方法。

①volumesに指定しているdatabase/dataディレクトリ配下のデータを一度全て削除
②削除が完了したら再度ビルド
③エラーが出てビルドが失敗するたびにdatabase/dataディレクトリ配下のデータを全て削除して再ビルドする。

※エラーはDocker Desktopの各コンテナからログを参照できる。

AnonymousNinjaAnonymousNinja

③ビルドしたコンテナで各ソースの準備

3-1. Nuxt3の準備

ビルドしたコンテナにログインし、Nuxt3プロジェクトを作成

cd docker
docker compose exec -it client bash

cd app/src/クローンしたプロジェクト名

# 今回はfrontendというプロジェクト名でプロジェクトを作成(ディレクトリ名がfrontendになってくれるので)
# @see https://nuxt.com/docs/getting-started/installation
# pnpm dlx nuxi@latest init <project-name>
pnpm dlx nuxi@latest init frontend

# 質問は「pnpm」「git initialize NO」で回答

cd frontend
# lsしてプロジェクトが生成されていたら🙆‍♂️
ls -la


3-2. Golang側の調整

cd docker
docker exec -it api ash

# Echoをインストール
go install github.com/labstack/echo/v4

# モジュールエントリーを作成し、go.modを生成
go mod init

# lsしてgo.modが生成されていることを確認
ls

VScodeでbackend配下にmain.goを作成する。
(もちろんtouchで作ってもOK)

main.goを以下のように編集
main.goの内容については、以下記事より引用。

▼参考
Go(Echo)+MySQLでCRUDを行えるAPIサーバをDocker上で構築する(2)APIサーバを構築する

package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    // Echoインスタンスを作成
    e := echo.New()

    // httpリクエストの情報をログに表示
    e.Use(middleware.Logger())
    // パニックを回復し、スタックトレースを表示
    e.Use(middleware.Recover())

    // ルートを設定(第一引数にエンドポイント、第二引数にハンドラーを指定)
    e.GET("/api", hello)

    // サーバーをポート番号8080で起動
    e.Logger.Fatal(e.Start(":8080"))
}

// ハンドラーを定義(どういう処理を実行するか)
func hello(c echo.Context) error {
    return c.String(http.StatusOK, "Hello, World!")
}

backend配下にdatabase/database.goを作成し、以下のように編集。
内容については、上記同様以下記事より引用

▼参考
Go(Echo)+MySQLでCRUDを行えるAPIサーバをDocker上で構築する(1)APIサーバを構築する

package database

import (
    "fmt"
    "errors"
    "os"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

// MySQLに作成するUserテーブルの定義
type User struct {
    // gorm.Modelをつけると、idとCreatedAtとUpdatedAtとDeletedAtが作られる
    gorm.Model
    Name string
    Age  int
}

// DBを起動させる
func dbInit() *gorm.DB {
    // [ユーザ名]:[パスワード]@tcp([ホスト名]:[ポート番号])/[データベース名]?charset=[文字コード]
    dsn := fmt.Sprintf(
            `%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True`,
            os.Getenv("MYSQL_USER"),
            os.Getenv("MYSQL_PASSWORD"),
            os.Getenv("MYSQL_HOST"),
            os.Getenv("MYSQL_DATABASE")
        )

    // DBへの接続を行う
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

    // エラーが発生した場合、エラー内容を表示
    if err != nil {
        log.Fatal(err)
    }
    // 接続に成功した場合、「db connected!!」と表示する
    fmt.Println("db connected!!")
    return db
}

func main() {
    // DB起動
    db := dbInit()
    // Userテーブル作成
    db.AutoMigrate(&User{})
}