0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pascalで作成したWebアプリをCloud Runへデプロイしてみた

Posted at

こんにちは。イワサキと申します。

こちらの記事は、Free PascalやDelphi向けのmORMot2フレームワーク(なんとDelphi 7にも対応!)を使って開発したWebアプリをCloud Buildでビルド、Cloud Runへデプロイする内容となります。

はじめに

DelphiでもWebアプリの開発は可能ですが、ProfessionalエディションはWindows向けのみとなっており、Linux環境向けにビルドするためにはEnterprise以上のエディションが必要となりコストが大きくなってしまいます。その点、Free PascalとmORMot2の組み合わせであれば安価に開発可能となります。
また、Delphiはクロスコンパイラなのに対してFree PascalはLinuxのネイティブコンパイラが存在するため、Cloud Buildで直接Linuxバイナリを生成できます。

mORMot2は初めて触れてみましたが、RTTIによるルーティングの自動生成やPascalでの扱いが難しいJSON形式のサポートなど、WEBアプリの開発に便利なライブラリが揃っております。
生成されたアプリもApacheやnginxなどのWebサーバを必要とせず1ファイルで動作する点も非常に手軽で良いです。
動作もネイティブコードなため当然高速です。

プロジェクトの構造

作成したソースコードはGitHubに置いてあります。一部AIを用いて作成と手直しをしています。
docker-compose.yamlを用意していますのでDockerをインストールするとローカルでも動かせます。

project/
├── Dockerfile
├── cloudbuild.yaml
├── docker-compose.yaml
└── app/
    ├── Makefile
    └── src/
        ├── main.dpr
        └── webapp.pas

Webアプリのプログラムコード

今回はCloud Runへのデプロイを試すことが目的のため、定数で用意した簡単なページを返すようにしました。
フロントエンドをファイルで用意する場合もビルド時にリソースとして取り込むことで、アプリを実行ファイル単体へ集約することが出来ます。また、アプリの起動時にメモリへ展開されますので、ブラウザからのリクエスト発生時にファイルアクセスが不要となります。
(dprは取り立てて特別な内容が無いため省略)

webapp.pas
unit WebApp;

interface

uses
  Classes,
  SysUtils,
  mormot.core.base,
  mormot.core.os,
  mormot.net.http,
  mormot.net.async;

type
  TWebApp = class
  private
    fHttpServer: THttpAsyncServer;
  protected
    function DoGetIndex(Ctxt: THttpServerRequestAbstract): cardinal;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

const
  PORT = '8080';

const INDEX_HTML: RawUtf8 = 
  '<!DOCTYPE html>' +
  '<html lang="ja">' +
  '<meta charset="UTF-8">' +
  '<title>' +
  'Web Application Sample' +
  '</title>' +
  '<body style="font-size:24px;">' +
  '<p>' +
  'Free Pascal + mORMot2' +
  '</p>' +
  '<p>' +
  'Simple Web Application by mORMot2' +
  '</p>' +
  '</body>' +
  '</html>';

function TWebApp.DoGetIndex(Ctxt: THttpServerRequestAbstract): cardinal;
begin
  Result := 200;
  Ctxt.OutContent := INDEX_HTML;
  Ctxt.OutContenttype := 'text/html; charset=utf-8';
end;

constructor TWebApp.Create;
begin
  inherited Create;
  fHttpServer := THttpAsyncServer.Create(
    '0.0.0.0:' + PORT, nil, nil, '', SystemInfo.dwNumberOfProcessors + 1, 30000, []);

  with fHttpServer do
  begin
    // ルーティング
    Route.Get('/', @DoGetIndex);

    WaitStarted;
  end;
end;

destructor TWebApp.Destroy;
begin
  fHttpServer.Free;
  inherited Destroy;
end;

end.

アプリのビルド環境

mORMot2はLazarusのLPI/LPKでの開発を前提としているようで公式のMakefileが付属しておりません。そのためlpkファイルを元にAIで生成して手直しをしました。
実行には後述のDockerfileでmORMot2のソースとstaticディレクトリ内のファイルの取得を前提としています。

Makefile
# ==============================================================================
# Makefile for mORMot 2 Application
# Located in: /app
# Assumes structure: /app/mORMot2, /app/src, /app/Makefile
# ==============================================================================

# --- Project Configuration ---
PROJECT_NAME = webapp
FPC = fpc
MAIN_SRC = src/main.dpr
SRCS = src/webapp.pas

# mORMot2 はホストには存在せず、Docker ビルド時に /app/mORMot2 へ取得されます。
# この Makefile はコンテナ内での実行を前提としています。
MORMOT2_ROOT = mORMot2

# --- Target Environment (FPC variables will be resolved at runtime) ---
TARGET_CPU = x86_64
TARGET_OS = linux

# --- Output Directories ---
BUILD_DIR = build
#UNIT_OUT_DIR = $(BUILD_DIR)/objs/$(TARGET_CPU)-$(TARGET_OS)
EXE_DIR = $(BUILD_DIR)/release

# --- 1. Unit Search Paths (-Fu) ---
CORE_PATHS = \
    -Fu$(MORMOT2_ROOT)/src \
    -Fu$(MORMOT2_ROOT)/src/core \
    -Fu$(MORMOT2_ROOT)/src/crypt \
    -Fu$(MORMOT2_ROOT)/src/db \
    -Fu$(MORMOT2_ROOT)/src/lib \
    -Fu$(MORMOT2_ROOT)/src/net \
    -Fu$(MORMOT2_ROOT)/src/orm \
    -Fu$(MORMOT2_ROOT)/src/rest \
    -Fu$(MORMOT2_ROOT)/src/soa \
    -Fu$(MORMOT2_ROOT)/src/script \
    -Fu$(MORMOT2_ROOT)/src/misc \
    -Fu$(MORMOT2_ROOT)/src/tools/mget

UNIT_PATHS = $(CORE_PATHS)

# --- 2. Compiler Defines (-d) ---
DEFINES = \
    -dFPCMM_SERVER \
    -dNOSYNDBZEOS \
    -dNOSYNDBIBX

# --- 3. Linker Options (-Fl for static libraries) ---
LINK_OPTS = -Fl$(MORMOT2_ROOT)/static/$(TARGET_CPU)-$(TARGET_OS)

# --- 4. Common Compiler Options ---
COMMON_OPTS = \
    -Mobjfpc \
    -O3 \
    $(DEFINES) \
    $(LINK_OPTS) \
    $(UNIT_PATHS)
#    -Fu$(UNIT_OUT_DIR) \

# ==============================================================================
# Targets
# ==============================================================================

all: $(EXE_DIR)/$(PROJECT_NAME)
.PHONY: all

# Executable Target
$(EXE_DIR)/$(PROJECT_NAME): $(MAIN_SRC) $(SRCS)
	@echo "--- Creating Output Directories (for executable) ---"
#	@mkdir -p $(UNIT_OUT_DIR)
	@mkdir -p $(EXE_DIR)
	@echo "--- Compiling $(PROJECT_NAME) ---"
	$(FPC) $(COMMON_OPTS) -FE$(EXE_DIR) -o$(PROJECT_NAME) $<
	@echo "--- Build successful: $(BUILD_DIR)/$(PROJECT_NAME) ---"

# Clean up intermediate (.ppu, .o) and executable files
clean:
	@echo "--- Cleaning up build artifacts ---"
	@rm -rf $(BUILD_DIR)

.PHONY: clean

Dockerイメージのビルド

実行環境は余計なツールを入れないようにするため、ビルドと実行でコンテナを分けビルド完了後に実行環境へコピーしています。
mORMot2には手を加えず使うため、プロジェクトのサイズを考慮してDockerイメージのビルドのタイミングで取得する事としました。
staticの中身は公式からアーキテクチャ別にコンパイルしたバイナリが提供されていますので、そちらをダウンロードして使います。

Dockerfile
# ==============================================================================
#   Stage 1: Builder (ビルド環境 - FPC 3.2.2 を明示的に使用)
# ==============================================================================
FROM ubuntu:22.04 AS builder

# 必要なパッケージのインストール: FPC本体(バージョン固定)、make、スクリプト依存ツール
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        fpc=3.2.2+dfsg-9ubuntu1 \
        make \
        wget \
        ca-certificates \
        p7zip-full \
        unzip && \
    rm -rf /var/lib/apt/lists/*

# mORMot2 を GitHub から取得
RUN wget -q -O /tmp/mormot2.zip https://github.com/synopse/mORMot2/archive/refs/tags/2.3.stable.zip && \
    unzip /tmp/mormot2.zip -d /opt/ && \
    rm /tmp/mormot2.zip

# mORMot2 static を公式サイトから取得
RUN wget -q -O /tmp/mormot2static.7z https://synopse.info/files/mormot2static.7z && \
    7za x /tmp/mormot2static.7z -o/opt/static && \
    rm /tmp/mormot2static.7z

# ホストからアプリケーションソースコードのコピー
COPY app /app

# mORMot2 関連のシンボリックリンクを作成
RUN ln -s /opt/mORMot2-2.3.stable /app/mORMot2 && \
    ln -s /opt/static /app/mORMot2/static

# Makefile を使用したアプリケーションのビルド
WORKDIR /app
RUN make all

# ==============================================================================
#   Stage 2: Runtime (実行環境 - 軽量化とCloud Run向け)
# ==============================================================================
FROM ubuntu:22.04

# 実行ディレクトリの設定
WORKDIR /usr/bin/

# 実行ファイルのコピー
COPY --from=builder /app/build/release/webapp /usr/bin/webapp

# ポートの公開
EXPOSE 8080

# コンテナ起動時に実行するコマンド
CMD ["/usr/bin/webapp"]

ここまででアプリの準備は完了です。続いてGCPの方を整備していきます。

GCPの環境構築 事前準備

こちらの手順でGoogle Cloud CLIをインストールしておきます。
今回のコマンド操作はWSLで確認しています。

GCPの環境構築

まず、Google Cloudコンソールへアクセスして新しいプロジェクトを作成して請求先を設定します。請求先は設定しますが今回のように小規模であれば無料枠で十分足ります。
Google Cloudコンソールで表示されるプロジェクトIDは後の作業で必要になりますのでメモしておきます。

こちらのコマンドで表示されるURLへアクセスしてログインを行います。

gcloud auth login

続いて、こちらのコマンドでプロジェクトを選択します。
PROJECT_IDは作成したプロジェクトIDを指定してください。(以後も同じ)

gcloud config set project PROJECT_ID

以下のコマンドでAPIの有効化、Artifact Registryの作成、SA(サービスアカウント)の作成など実行します。
今回は専用にSAを作成します。
途中のコマンドでSAを指定する必要がありますが、Cloud ConsoleのIAMなどで確認可能です。

#APIの有効化コマンド
gcloud services enable run.googleapis.com
gcloud services enable cloudbuild.googleapis.com
gcloud services enable artifactregistry.googleapis.com

#デプロイ用のサービスアカウントを作成する
gcloud iam service-accounts create webapp-deployer \
  --display-name="WebApp Deployment Service Account"
#アーティファクトレジストリを作成
gcloud artifacts repositories create webapp \
  --repository-format=docker \
  --location=asia-northeast1 \
  --description="Web app container repository"

#サービスアカウントへの権限設定
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:webapp-deployer@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/run.admin

gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:webapp-deployer@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/iam.serviceAccountUser

gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:webapp-deployer@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/artifactregistry.writer

gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:webapp-deployer@PROJECT_ID.iam.gserviceaccount.com  \
  --role=roles/storage.objectAdmin

gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:webapp-deployer@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/logging.logWriter

ビルドとデプロイ

Cloud Build用のyamlになります。取り立てて特別な事は行っておりません。
※Step4はインターネット上でCloud Runを公開する設定となっております。確認が終わりましたらCloud Runを破棄するかallUsersからroles/run.invokerの設定を削除してください。

cloudbuild.yaml
# cloudbuild.yaml

options:
  logging: CLOUD_LOGGING_ONLY

# ==============================================================================
#   Step 1: Build container image using Dockerfile
# ==============================================================================
steps:
  - name: "gcr.io/cloud-builders/docker"
    id: "Build image"
    args:
      [
        "build",
        "-t",
        "asia-northeast1-docker.pkg.dev/$PROJECT_ID/webapp/webapp:latest",
        "."
      ]

# ==============================================================================
#   Step 2: Push to Artifact Registry
# ==============================================================================
  - name: "gcr.io/cloud-builders/docker"
    id: "Push image"
    args:
      [
        "push",
        "asia-northeast1-docker.pkg.dev/$PROJECT_ID/webapp/webapp:latest"
      ]

# ==============================================================================
#   Step 3: Deploy to Cloud Run (using gcloud)
# ==============================================================================
  - name: "gcr.io/cloud-builders/gcloud"
    id: "Deploy to Cloud Run"
    args:
      [
        "run",
        "deploy",
        "webapp",
        "--image=asia-northeast1-docker.pkg.dev/$PROJECT_ID/webapp/webapp:latest",
        "--region=asia-northeast1",
        "--platform=managed",
        "--port=8080",
        "--cpu-boost"
      ]

# ==============================================================================
#   Step 4: Add IAM policy — allow allUsers as Cloud Run Invoker
# ==============================================================================
  - name: gcr.io/cloud-builders/gcloud
    args:
      - run
      - services
      - add-iam-policy-binding
      - webapp
      - --region=asia-northeast1
      - --member=allUsers
      - --role=roles/run.invoker

images:
  - "asia-northeast1-docker.pkg.dev/$PROJECT_ID/webapp/webapp:latest"

プロジェクトのルートディレクトリで、こちらのコマンドを実行します。
PROJECT_IDはプロジェクトのIDに置き換えてください。

gcloud builds submit \
  --service-account=projects/PROJECT_ID/serviceAccounts/webapp-deployer@PROJECT_ID.iam.gserviceaccount.com

実行結果の確認

ビルドとデプロイが終わったら表示されたURLまたはGoogle CloudコンソールのCloud Runで表示されるURLへブラウザでアクセスすると、Webアプリから送信されたページの内容を確認できます。
アプリのプログラムを変更して"gcloud builds submit"コマンドを実行すると、プログラムが変更させることを確認できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?