【Perl講座 第8回】最終回!実践アプリ開発!簡易ブログシステムの構築とPSGIデプロイ完全ガイド

「動く」から「運用する」へ。エンジニアへの最後のステップ。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
全8回にわたるPerl講座も、ついに最終回を迎えました。

第1回の「Hello World」から始まり、変数の扱い、ファイル操作、データベース連携、そしてフレームワークの導入と、皆さんは着実にスキルアップしてきました。
今回は、その集大成として「ブログシステム」を作り上げ、さらにそれを「プロの現場と同じ構成」で公開(デプロイ)します。

コウ君

先生、ついに最終回ですね…!
前回Mojoliciousを使ってみて、Perlってこんなに書きやすいんだって感動しました。
でも、morbo コマンドで動かしてると、ターミナルを閉じたら止まっちゃいますよね?
本番のWebサイトみたいに、サーバー再起動しても勝手に動くようにするにはどうすればいいんですか?

リナックス先生

良い質問ね。
開発用の morbo はあくまでテスト用。本番では Hypnotoad(ヒプノトード) という強力なサーバーを使うの。
さらに、Linuxの「Systemd」に登録して自動起動させたり、Apacheを「受付係(リバースプロキシ)」として前に立たせるのが鉄板の構成よ。
最後は、作ったアプリを「世界中に安定して公開する技術」を身につけましょう!

本記事では、記事の投稿・編集・削除ができるブログアプリを開発し、AlmaLinux 9 上で「Apache + Hypnotoad」という最強の構成で稼働させるまでの全手順を解説します。


第1章:ブログシステムの設計とDB構築

まずは、今回作成するブログシステムの仕様を決め、データベースを準備します。
基本機能は「CRUD(Create, Read, Update, Delete)」です。

1. 機能要件

  • 一覧画面: 記事タイトルと作成日時の一覧を表示。
  • 詳細画面: 記事の本文を表示。
  • 投稿画面: タイトルと本文を入力して保存。
  • 編集・削除: 既存の記事を修正または削除する。

2. データベースの準備

第5回で作成したMariaDBの bbs_db を再利用し、新しいテーブル articles を作成します。

mysql -u bbs_user -p bbs_db

SQL実行:

CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

第2章:アプリケーションの実装 (Mojolicious::Lite)

それでは、コーディングに入ります。
myblog.pl というファイルを作成します。

mkdir -p /home/admin/myblog
cd /home/admin/myblog
nano myblog.pl

myblog.pl の全コード

少し長いですが、これがWebアプリの全容です。
ヘルパー、ルーティング、コントローラー、テンプレートが1つにまとまっています。

#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use DBI;

# --- 設定 ---
# データベース接続ヘルパー
helper db => sub {
    my $dsn = "DBI:mysql:database=bbs_db;host=localhost";
    my $user = "bbs_user";
    my $pass = "password123";
    my %attr = (
        PrintError => 0, RaiseError => 1,
        mysql_enable_utf8 => 1, AutoCommit => 1,
        mysql_init_command => "SET NAMES utf8mb4"
    );
    state $dbh = DBI->connect($dsn, $user, $pass, \%attr);
    return $dbh;
};

# --- ルーティング ---

# 一覧画面 (Read)
get '/' => sub ($c) {
    my $sth = $c->db->prepare("SELECT * FROM articles ORDER BY id DESC");
    $sth->execute;
    $c->render(template => 'index', articles => $sth->fetchall_arrayref({}));
} => 'index';

# 新規作成画面
get '/create' => sub ($c) {
    $c->render(template => 'create');
};

# 保存処理 (Create)
post '/store' => sub ($c) {
    my $title = $c->param('title');
    my $content = $c->param('content');
    
    if ($title && $content) {
        $c->db->do("INSERT INTO articles (title, content) VALUES (?, ?)", undef, $title, $content);
        $c->flash(message => '記事を投稿しました。');
        $c->redirect_to('index');
    } else {
        $c->flash(error => 'タイトルと本文は必須です。');
        $c->redirect_to('create');
    }
};

# 詳細画面 (Read)
get '/entry/:id' => sub ($c) {
    my $id = $c->param('id');
    my $article = $c->db->selectrow_hashref("SELECT * FROM articles WHERE id = ?", undef, $id);
    
    return $c->reply->not_found unless $article;
    $c->render(template => 'show', article => $article);
};

# 削除処理 (Delete)
post '/delete/:id' => sub ($c) {
    my $id = $c->param('id');
    $c->db->do("DELETE FROM articles WHERE id = ?", undef, $id);
    $c->flash(message => '記事を削除しました。');
    $c->redirect_to('index');
};

app->start;

__DATA__

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head>
    <title>My Perl Blog</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
</head>
<body class="container mt-4">
    <h1><%= link_to 'My Perl Blog' => 'index' %></h1>
    <hr>
    <% if (my $msg = flash 'message') { %>
        <div class="alert alert-success"><%= $msg %></div>
    <% } %>
    <% if (my $err = flash 'error') { %>
        <div class="alert alert-danger"><%= $err %></div>
    <% } %>
    <%= content %>
</body>
</html>

@@ index.html.ep
% layout 'default';
<div class="mb-3">
    <%= link_to '新規記事を書く' => 'create', class => 'btn btn-primary' %>
</div>
<div class="list-group">
    % for my $article (@$articles) {
        <a href="<%= url_for('show', id => $article->{id}) %>" class="list-group-item list-group-item-action">
            <h5 class="mb-1"><%= $article->{title} %></h5>
            <small><%= $article->{created_at} %></small>
        </a>
    % }
</div>

@@ create.html.ep
% layout 'default';
<h2>新規投稿</h2>
<form action="<%= url_for('store') %>" method="POST">
    <div class="mb-3">
        <label>タイトル</label>
        <input type="text" name="title" class="form-control">
    </div>
    <div class="mb-3">
        <label>本文</label>
        <textarea name="content" class="form-control" rows="5"></textarea>
    </div>
    <button type="submit" class="btn btn-success">保存</button>
</form>

@@ show.html.ep
% layout 'default';
<h2><%= $article->{title} %></h2>
<p class="text-muted"><%= $article->{created_at} %></p>
<div class="lead">
    <%== $article->{content} %> 
</div>
<hr>
<form action="<%= url_for('delete', id => $article->{id}) %>" method="POST" onsubmit="return confirm('本当に削除しますか?');">
    <button type="submit" class="btn btn-danger">この記事を削除</button>
</form>

ポイント解説

  • flash: リダイレクトした直後の1回だけ表示されるメッセージ。「投稿しました」などの通知に使います。
  • Bootstrap CDN: 簡単に見た目を整えるために、BootstrapのCSSを読み込んでいます。
  • url_for: ルーティング名(’index’や’store’)からURLを自動生成するヘルパー。リンク切れを防げます。

第3章:本番サーバー「Hypnotoad」の導入

開発用サーバー(morbo)は便利ですが、シングルプロセスであり、アクセスが集中すると捌ききれません。
本番では、Mojoliciousに付属する「Hypnotoad(ヒプノトード)」を使います。

Hypnotoadの特徴

  • プリフォーク型: 複数のプロセス(ワーカー)をあらかじめ立ち上げておくため、大量のアクセスを並列処理できます。
  • ホットデプロイ: ユーザーの接続を切断することなく、新しいコードを反映(再起動)できます。

設定ファイル (myblog.conf) の作成

Hypnotoadの設定を記述します。

nano myblog.conf

内容:

{
  hypnotoad => {
    listen  => ['http://127.0.0.1:8080'], # ローカルの8080番で待機
    workers => 4,                         # ワーカー数
    proxy   => 1                          # リバースプロキシ配下で動く設定
  }
}

第4章:Systemdによるサービス化(自動起動)

サーバーを再起動しても自動的にアプリが立ち上がるように、Systemdのユニットファイルを作成します。
これで、systemctl コマンドで管理できるようになります。

ユニットファイルの作成

sudo nano /etc/systemd/system/myblog.service

内容:
※UserとGroupは、アプリを配置したユーザー(例: admin)に合わせてください。
※パスは環境に合わせて書き換えてください。

[Unit]
Description=My Perl Blog Service
After=network.target mariadb.service

[Service]
Type=forking
User=admin
Group=admin
WorkingDirectory=/home/admin/myblog
ExecStart=/usr/local/bin/hypnotoad /home/admin/myblog/myblog.pl
ExecReload=/usr/local/bin/hypnotoad /home/admin/myblog/myblog.pl
KillMode=process

[Install]
WantedBy=multi-user.target

サービスの起動と有効化

# 設定反映
sudo systemctl daemon-reload

# 起動
sudo systemctl start myblog

# 自動起動ON
sudo systemctl enable myblog

# ステータス確認
sudo systemctl status myblog

active (running) と表示されれば、バックグラウンドで8080番ポートで稼働しています。


第5章:Apacheリバースプロキシの設定

最後に、インターネット(80番/443番)からのアクセスを、裏で動いているHypnotoad(8080番)に転送する設定を行います。

Apache設定ファイルの作成

sudo nano /etc/httpd/conf.d/myblog.conf

内容:

<VirtualHost *:80>
    ServerName your-server-ip
    
    # プロキシ設定
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

SELinuxの設定(超重要)

AlmaLinuxでは、デフォルトで「Apacheが外部(127.0.0.1含む)へネットワーク接続すること」が禁止されています。
これを許可しないと 503 Service Unavailable エラーになります。

sudo setsebool -P httpd_can_network_connect 1

Apache再起動

sudo systemctl restart httpd

第6章:動作確認とホットデプロイ運用

ブラウザでサーバーのIPアドレスにアクセスしてみましょう。
「My Perl Blog」が表示され、記事の投稿や削除ができれば完成です!

修正を反映する場合

今後、myblog.pl を修正した際、わざわざサービスを停止する必要はありません。
以下のコマンドを打つだけで、ユーザーにエラーを見せることなく、シームレスに新バージョンへ切り替わります。

sudo systemctl reload myblog

これが、Hypnotoadが提供する「ホットデプロイ」の威力です。


全8回まとめ:あなたはもう「Perlエンジニア」だ

お疲れ様でした!
長い道のりでしたが、あなたは今、以下のスキルを習得しました。

習得スキルチェックリスト:

  • Linuxサーバー環境構築 (AlmaLinux, Apache, MariaDB)
  • Perlの基礎文法 (スカラ, 配列, ハッシュ, 制御構文)
  • CGIの仕組み (HTTPヘッダー, GET/POST)
  • ファイル操作とデータベース連携 (DBI/DBD)
  • CPANモジュールの活用
  • Webフレームワーク(Mojolicious)での開発
  • セキュリティ対策 (XSS, CSRF, SQLインジェクション)
  • 本番環境デプロイ (PSGI, Systemd, リバースプロキシ)

Perlは「古い」と言われることもありますが、Linuxサーバーがある限り、Perlはそこに必ず存在します。
テキスト処理の強さ、後方互換性の高さ、そしてCPANという巨大な資産。
これらを使いこなせるあなたは、インフラエンジニアとしても、バックエンドエンジニアとしても、強力な武器を手に入れたことになります。

この講座はこれで終わりですが、プログラミングの旅は終わりません。
次は、自分で考えたオリジナルのツールを作ってみたり、他の言語(PythonやGo)と比較してみたりして、さらに技術を深めていってください。

リナックス先生とコウ君は、あなたのエンジニアライフをいつまでも応援しています。
またどこかの講座でお会いしましょう! Happy Hacking!

▼ エンジニアとしてのキャリアを加速させる ▼

オリジナルアプリを公開
「VPS」で自分専用環境

おすすめVPSを見る

Perlスキルを活かす
「ITエンジニア転職」

転職エージェントを見る

コメント