about blog links

このサイトの構成について

いつの日か撮った置物の犬

いつの日か撮った置物の犬

🐤 はじめに

こんにちは。yanoseaです。
このサイトをデプロイしてから見えてきた、「あれもしたい、これもしたい」を色々やっていました。
現時点で自分が納得するところまで手直しできたので、このタイミングでこのサイトの構成について書いてみたいと思います。
筆者が記事執筆初心者のため、こちらの記事を参考に書いていきます。

  • この記事はなに?

    • アウトプットを頑張っていく!という目標にむけて手始めにこのサイトの技術構成を解説兼備忘録として書き残しておいたものです
    • 個人サイトを開設したい方は参考になるかもしれません
  • この記事の想定読者は?

    • 筆者 (備忘録なので)
    • 個人サイトを開設してみたい方
  • この記事のねらいは?

    • 筆者のアウトプット実践
    • 筆者の備忘録
    • 各技術についての情報を少しでも解説する

⚠️ ご注意

  • 内容に筆者の個人的な解釈を多分に含んでいることにご注意ください
  • 間違った内容を書いている可能性があるためお気付きの場合はコメントやTwitter等でお気兼ねなくご指摘いただけますと幸いです

🏁 結論

構成は下図のとおりです。

yanosea.orgの構成図

🏗️ インフラ

このサイトはXserver VPSのLinuxマシンにデプロイされています。
昨今だとNetlifyVercelにデプロイするのがパフォーマンス面やコスト面で優れていると思っています。
では、何故VPS上にデプロイしているのか?それは…。

  • どこからでもSSHでいじれるLinuxマシンが欲しかった (建前)
  • 先にVPSやドメインを契約してしまった (本音)

自分のサイトを作りたいと思っていたけれどなかなか動けなかったので、自分を追い込むために先んじてインフラに投資したからです。
結果的にどこでもLinuxマシンに接続して色々試せる環境を手に入れたので、これはこれでいいかと自分に腑に落とさせています。
ただ、やはりパフォーマンスとコストは大事なので1年の契約が切れる頃には乗り換えられるように準備しようかなと早々に考えています。

参考程度にXserverに支払った金額を書いておきます。キャンペーンがちょっとありがたいですね。

VPS料金 : ¥8,311

VPSの料金

ドメイン料金 : ¥946

ドメインの料金

SSL証明書発行料金 : ¥1,045

SSL証明書発行の料金

合計 : ¥10,302

Xserverを選択した技術的な理由は特にありません。
契約した当時のTwitterのTLが「Xserverはいいぞ」と盛り上がっていたので飛びついただけです。

🖥️ サーバー構成

OSはArch Linuxを採用しています。Archを選択した理由はメインの私用PCのWindowsのWSLで使っているためです。
そして、このサイトはそのVPSのLinux上で稼働する2台のDockerコンテナで運用しています。
ひとつはWebサーバーが稼働しているコンテナで、もうひとつはAPIサーバーが稼働しているコンテナです。

このVPS自体はXserverの設定からポート80と443をWebサーバー用に開放しています。
また、GitHub Actionsおよび筆者が接続するためにエフェメラルポートを一つを開放してSSH接続に使用しています。
SSH接続するためにsshdの設定をしていますが、ここではこれ以上触れないでおきます。

🐋 Docker

サーバー運用にあたって、コンテナを用いて稼働させることにしました。
Dockerを選択した理由はVPSを必要以上に汚したくなかったのと、VPSを引っ越すことになっても再利用できると考えたからです。
恥ずかしながら業務で使用したことがなかったので、使ってみたかったというのもあります。

docker-composeを使用してDockerコンテナの起動や終了をコントロールしています。
複数のコンテナの操作・管理を簡単にしてくれるツールです。
Dockerfileと呼ばれるコンテナの設定ファイルをWebサーバーの分とAPIサーバーの分で2つ用意し、
docker-compose用の設定ファイルdocker-compose.ymlからそれらを参照します。
インフラをコード化しておく、いわゆるInfrastracture as Code (IaC)というやつですね。
一般に以下のメリットがあると言われています。

  • なるべく手作業を減らし、ヒューマンエラーを防止する
  • 保守性の向上

個人的には設定ファイルを見れば使用しているコンテナが一目でわかるところにメリットを感じています。

docker-compose.ymlは以下のとおりです。

version: "3.8"

services:
  back:
    container_name: $BACK_CONTAINER_NAME
    build:
      context: ./back
      dockerfile: Dockerfile
      args:
        - BACK_PORT=$BACK_PORT
    ports:
      - $BACK_PORT:$BACK_PORT
    env_file: .env

  front:
    container_name: $FRONT_CONTAINER_NAME
    build:
      context: ./front
      dockerfile: Dockerfile
      args:
        - FRONT_PORT=$FRONT_PORT
    ports:
      - $FRONT_PORT:$FRONT_PORT
    env_file: .env
    volumes:
      - ./$FRONT_SSL_PATH:/etc/nginx/ssl:ro

2つのコンテナ設定がservices配下に書かれているのがお分かりいただけるかと思います。
backと命名されているものがAPIサーバーのコンテナで、frontと命名されているものがWebサーバーのコンテナです。

$BACK_PORTなどの環境変数を使用していますが、これは.envファイルで設定しているものです。
.envという名前のファイルがdocker-compose.ymlと同階層に配置してあれば、
docker-compose実行時に読み込んでdocker-compose.ymlの中で展開してくれます。
このおかげでdocker-composeを使用するshellに環境変数を設定しておく必要がなくなります。

また、Webサーバー側のコンテナに$FRONT_SSL_PATHという環境変数を使用しています。中身はVPSのとあるパスです。
このパスに向けてGitHub Actionsとsecrets変数を用いてSSL証明書と鍵ファイルを書き込み、
Webサーバー側のコンテナにマウントしてhttps接続に対応しています。

🌐 Webサーバー (front)

WebサーバーはNginxを採用しました。
Nginxを採用した理由ですが、まずシェアの観点からNginxとApacheの二択に絞っていました。
最終的には業務での運用経験があまりないNginxを選択するに至りました。

公開先のディレクトリにAstroでビルドした静的サイトを配置しています。
このコンテナの設定にあたるDockerfileは以下のとおりです。

FROM node:21.6-slim as builder
WORKDIR /app
COPY package.json ./
COPY pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm build

FROM nginx:alpine AS runtime
ARG FRONT_PORT
COPY nginx.conf.template /etc/nginx/templates/nginx.conf.template
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE $FRONT_PORT

ポイントは2点あります。

  1. マルチステージビルドの採用
    Nginxのコンテナ起動時にAstroで静的サイトのビルドNginxの立ち上げとビルドした静的サイトのデプロイ
    2ステップを踏むようにしています。
    これにより立ち上がったコンテナ内にソースコードやnode_modulesなどの不要なファイル群が残ってしまうことを防ぐことができ、
    コンテナの軽量化とクリーンな環境でのNginx稼動が実現しています。

  2. Nginx設定ファイルの適用
    COPY nginx.conf.template /etc/nginx/templates/nginx.conf.template
    nginxの設定のテンプレートファイルをコンテナ内へコピーしています。
    これにより立ち上がったコンテナで稼働するNginxのリバースプロキシやSSLなどの設定を事前にファイルで保持しておくことができます。

📡 APIサーバー(back)

Golangでビルドしたバイナリを実行してAPIサーバーを立てています。
.envファイルにエフェメラルポート番号を定義して、docker-compose.yml経由でDockerfileへ渡してListenさせています。
クライアントからAPIのエンドポイントへのアクセスをNginxで一度受け止めて、
リバースプロキシでAPIサーバーへのリクエストをルーティングしています。
このコンテナの設定にあたるDockerfileは以下のとおりです。

FROM golang:1.22 as builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /main .

FROM alpine:latest
ARG BACK_PORT
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /main ./
EXPOSE $BACK_PORT
CMD ["./main"]

こちらもマルチステージビルドを採用しています。
APIサーバーではSpotifyのWeb APIを実行して筆者の再生中、または最後に再生したSpotify上の楽曲の情報を取得して
クライアントに返却しています。

🧑‍🚀 フロントエンド実装

フロントエンドはAstroというWebフレームワークを使用しました。
Astroについてはこちらの記事がとても分かりやすく簡潔に書かれていたため、この紹介のみとさせていただきます。
CSSフレームワークにはtailwindcssを使用しています。

筆者は昨今話題によく上がるモダンなフロントエンド技術を今までの業務で使用したことがありません。
(.NET MVC + jQuery とかSpring Boot + jQuery みたいな構成をずっとやっていました。業務システム開発あるある?)
主観ですがフロントエンド周りの技術は複雑で難しいイメージがあり、
シンプルかつ管理が楽なものが良いと考えたためこれらの採用に至りました。
今後はReactやVue.jsなどを使ったものを作って遊んでみたいです。

🐀 バックエンド実装

バックエンドはGolangを採用しました。 Web API フレームワークとしてEchoを採用しました。
Golangを選択した理由はTwitterでつながりのある方たちがよく使ってらっしゃり、
筆者自身も業務で経験のない比較的モダンな言語を使ってみたかったからです。
Echoは単純に「Go API フレームワーク」なんて検索をかけて、一番注目度が高いように見えたからです。
結果的にシンプルに書けるものだったのでかなり気に入っています。

🚀 CD

デプロイはGitHub Actionsで自動化しています。デプロイはmainブランチにpushされた時に実行されます。
リポジトリの設定でmainブランチに直接pushするのは制限しているため、PRをマージした時のみ実行されます。

workflowの設定ファイルは以下のとおりです。

name: deploy
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    steps:
      - name: deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST_IP }}
          username: ${{ secrets.SSH_USER_NAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.SSH_PORT }}
          script: |
            sudo pacman -Syyu --noconfirm
            cd ${{ secrets.REPOSITORY_PATH }}
            sh ${{ secrets.DOCKER_STOP_SCRIPT }}
            cd
            rm -fr ${{ secrets.REPOSITORY_PATH }}
            git clone ${{ secrets.REPOSITORY_URL }} ${{ secrets.REPOSITORY_PATH }}
            cd ${{ secrets.REPOSITORY_PATH }}
            echo "${{ secrets.ENV_FILE }}" > .env
            mkdir -p  ${{ secrets.SSL_PATH }}
            echo "${{ secrets.SSL_CRT }}" > ${{ secrets.SSL_PATH }}/${{ secrets.SSL_CRT_FILE_NAME }}
            echo "${{ secrets.SSL_PRIVATE_KEY }}" > ${{ secrets.SSL_PATH }}/${{ secrets.SSL_PRIVATE_KEY_FILE_NAME }}
            sh ${{ secrets.DOCKER_START_SCRIPT }}

ワークフローは以下のステップを踏んでいます。

  1. secrets変数を使用してVPSへSSH接続
  2. 各コンテナの終了用スクリプトの実行
  3. ローカルリポジトリの削除
  4. ポートフォリオサイト用リポジトリのクローン
  5. secrets変数を使用して本番環境用.envファイルを作成
  6. secrets変数を使用してWEBサーバーのコンテナにマウントするSSL証明書関連ファイルを作成
  7. 各コンテナの開始用スクリプトの実行

🐔 おわりに

ここまで読んでいただきありがとうございます。(ほんと心から…。)
拙い文章でしたが、ある程度ねらいを達成できるような内容を書けたかなと思っています。

以降はもっとライトな記事を雑に投稿していこうと思っています。よろしければまた記事を読んでいただければ幸いです!

ではまた!

2024-05-07

#tech #astro #golang #docker #xserver
yanosea