こんにちは。イワサキと申します。
こちらの記事は、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は取り立てて特別な内容が無いため省略)
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 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の中身は公式からアーキテクチャ別にコンパイルしたバイナリが提供されていますので、そちらをダウンロードして使います。
# ==============================================================================
# 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
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"コマンドを実行すると、プログラムが変更させることを確認できます。