「動く」から「運用する」へ。エンジニアへの最後のステップ。
こんにちは!「LINUX工房」管理人の「リナックス先生」です。
全8回にわたるPerl講座も、ついに最終回を迎えました。
第1回の「Hello World」から始まり、変数の扱い、ファイル操作、データベース連携、そしてフレームワークの導入と、皆さんは着実にスキルアップしてきました。
今回は、その集大成として「ブログシステム」を作り上げ、さらにそれを「プロの現場と同じ構成」で公開(デプロイ)します。
先生、ついに最終回ですね…!
前回Mojoliciousを使ってみて、Perlってこんなに書きやすいんだって感動しました。
でも、morbo コマンドで動かしてると、ターミナルを閉じたら止まっちゃいますよね?
本番のWebサイトみたいに、サーバー再起動しても勝手に動くようにするにはどうすればいいんですか?
良い質問ね。
開発用の morbo はあくまでテスト用。本番では Hypnotoad(ヒプノトード) という強力なサーバーを使うの。
さらに、Linuxの「Systemd」に登録して自動起動させたり、Apacheを「受付係(リバースプロキシ)」として前に立たせるのが鉄板の構成よ。
最後は、作ったアプリを「世界中に安定して公開する技術」を身につけましょう!
本記事では、記事の投稿・編集・削除ができるブログアプリを開発し、AlmaLinux 9 上で「Apache + Hypnotoad」という最強の構成で稼働させるまでの全手順を解説します。
🐪 Perlサーバサイドプログラミング講座(バックナンバー)
これまでの知識がすべて繋がります。
- 【第1回】Linuxの「最強の武器」を手にせよ!環境構築とCGIでのHello World
- 【第2回】入力フォームを攻略せよ!GET/POSTデータの受け取りと変数入門
- 【第3回】掲示板を作ろう(前編)!制御構文とファイル操作(読み書き)の基礎
- 【第4回】車輪の再発明を防ぐ!CPANモジュールの導入とCGI.pmの活用
- 【第5回】データはデータベースへ!MariaDB(MySQL)連携とDBI/DBDモジュール
- 【第6回】モダンPerlの世界へ!Webフレームワーク「Mojolicious」入門
- 【第7回】セキュリティと見た目の分離!テンプレートエンジンの利用とXSS対策
- 【第8回】実践アプリ開発!簡易ブログシステムの構築とPSGIデプロイ
第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」で自分専用環境
Perlスキルを活かす
「ITエンジニア転職」

コメント