サーバ - HTTP API
概要
HTTP APIは、HTTPプロトコルを使用して、クライアントとサーバ間でデータのやり取りを行うインターフェースである。
HTTPメソッド (GET、POST、PUT、DELETE等) を使用して、サーバ上の機能やデータへのアクセスを実現する。
HTTP APIは、必ずしもRESTの設計原則に従う必要はなく、RPC形式やカスタムエンドポイント設計等、柔軟なアプローチが可能である。
用途や要件に応じて、シンプルな関数呼び出し型のAPI、バッチ処理API、ストリーミングAPI等、様々な形態で構築されている。
サーバの実装において、Linuxディストリビューションに必要なプログラム言語とフレームワークをインストールする。
一般的な選択肢として、PythonのFlaskやFastAPI、Node.jsのExpress、RubyのSinatra、JavaのSpring Boot、Go言語のGin等がある。
以下の例では、PythonのFlaskを使用した基本的なHTTP APIを構築している。
from flask import Flask, request, jsonify
app = Flask(__name__)
# 計算処理のエンドポイント例
@app.route('/api/calculate', methods=['POST'])
def calculate():
data = request.get_json()
operation = data.get('operation')
values = data.get('values', [])
if operation == 'sum':
result = sum(values)
elif operation == 'average':
result = sum(values) / len(values) if values else 0
else:
return jsonify({'error': 'Unknown operation'}), 400
return jsonify({'result': result})
# データ取得のエンドポイント例
@app.route('/api/status', methods=['GET'])
def get_status():
return jsonify({'status': 'running', 'version': '1.0.0'})
if __name__ == '__main__':
app.run(debug=True)
セキュリティでは、APIキーの実装、HTTPS通信の設定、入力データのバリデーション、SQLインジェクション対策等を行う必要がある。
また、認証・認可の仕組みとして、JWTトークン、Basic認証、OAuth2.0の実装も検討する必要がある。
パフォーマンスとスケーラビリティでは、キャッシュの実装 (例: Redis、Memcached)、データベースの最適化 (インデックス設計等)、ロードバランサの設置が重要である。
また、コンテナ化 (Docker、Podman等) やオーケストレーション (Kubernetes) の導入も検討する。
APIのドキュメント化も重要な要素となる。
Swagger / OpenAPI、API Blueprint、Postmanコレクション等を使用することにより、APIの仕様を明確に定義して、自動的にドキュメントを生成することができる。
これにより、他の開発者がAPIを理解および使用することが容易になる。
モニタリングとログ収集の設定において、PrometheusやGrafanaを使用してメトリクスを収集・可視化して、
ELKスタック (Elasticsearch、Logstash、Kibana) またはLoki等でログを収集・分析することにより、APIの健全性を監視することができる。
HTTP APIサーバを構築・運用する場合は、以下に示す事柄に注意する。
- 定期的なバックアップの実施
- セキュリティアップデートの適用
- アクセスログの監視
- レート制限の実装
- 適切なエラーハンドリングとHTTPステータスコードの使用
- データベースの最適化
- APIドキュメントの作成と管理
- タイムアウト設定の最適化
- CORS (Cross-Origin Resource Sharing) の適切な設定
サーバ環境のインストール
まず、システムの更新を行う。
# RHEL sudo dnf update # SUSE sudo zypper update # Raspberry Pi sudo apt update sudo apt upgrade
次に、Linuxサーバに必要な基本環境をインストールする。
# RHEL sudo dnf install curl wget git # SUSE sudo zypper install curl wget git # Raspberry Pi sudo apt install curl wget git build-essential
開発言語とフレームワークの選択
Python + Flaskを使用する場合
Pythonと仮想環境をインストールする。
# RHEL sudo dnf install python3 python3-pip python3-virtualenv # SUSE sudo zypper install python3 python3-pip python3-virtualenv # Raspberry Pi sudo apt install python3 python3-pip python3-venv
Pythonの仮想環境を作成する。
これにより、プロジェクトごとに独立したPython環境を作成することができる。
python3 -m venv <作成する仮想環境のディレクトリ名>
Pythonの仮想環境をアクティベートする。
source venv/bin/activate
必要なPythonライブラリをインストールする。
pip install flask flask-cors requests
※注意
venv/bin/activateスクリプトを、~/.profileファイル等に設定することは非推奨である。
activateスクリプトは相対パスで動作するため、フルパスで指定する必要がある。
これは、常に特定の仮想環境がアクティブになってしまうため、他のプロジェクトで異なる仮想環境を使用する場合に問題が発生するからである。
また、シェルのセッションごとに自動的にアクティベートすると、どの環境で作業しているのか理解しにくい。
代替案として、以下に示すような方法が推奨される。
以下に示すような方法ならば、必要な時のみ仮想環境をアクティベートすることが可能なため、柔軟な運用が可能になる。
特に、複数のプロジェクトを扱う場合は、2番目や3番目の方法が便利である。
# 方法 1
# エイリアスを設定する方法 (~/.profileファイル等に記述)
alias activate-myproject='source <activateスクリプトのパス>'
# 方法 2
# プロジェクトディレクトリに入った時のみ自動的にアクティベートする方法 (~/.profileファイル等に記述)
function cd()
{
builtin cd "$@"
# プロジェクトディレクトリに入った時のみ仮想環境をアクティベート
if [ -d "venv" ]; then
# 既に仮想環境がアクティブでない場合のみ
if [ -z "$VIRTUAL_ENV" ]; then
source venv/bin/activate
fi
fi
}
# 方法 3
# direnvのような専用ツールを使用する方法
# プロジェクトディレクトリの.envrcファイルに記述
layout python3 # Pythonの仮想環境を自動的に管理
Node.js + Expressを使用する場合
Node.jsをインストールする。
Node.jsの詳細なインストール手順は、インストール_-_Node.jsを参照すること。
プロジェクトディレクトリを作成する。
mkdir <任意のディレクトリ 例 : ~/my-api> cd <作成したディレクトリ 例 : ~/my-api>
プロジェクトに必要なJavaScriptライブラリをインストールする。
npm init -y npm install express body-parser cors helmet
Go言語を使用する場合
Go言語をインストールする。
# RHEL sudo dnf install golang # SUSE sudo zypper install go # Raspberry Pi sudo apt install golang
プロジェクトディレクトリを作成して、Go Modulesを初期化する。
mkdir <任意のディレクトリ 例: ~/my-api> cd <作成したディレクトリ 例: ~/my-api> go mod init <モジュール名 例: myapi>
必要なパッケージをインストールする。
go get github.com/gin-gonic/gin go get github.com/rs/cors
データベースの設定
PostgreSQLを使用する場合
PostgreSQLをインストールする。
# RHEL sudo dnf install postgresql postgresql-server libpq-devel # SUSE sudo zypper install postgresql postgresql-server libpq5 # Raspberry Pi sudo apt install postgresql postgresql-contrib libpq-dev
PostgreSQLデータベースクラスタを初期化する。
# RHEL / SUSE sudo postgresql-setup --initdb # Raspberry Pi # インストール時に自動的に初期化される
PostgreSQLサービスを起動して、自動起動を有効にする。
sudo systemctl start postgresql sudo systemctl enable postgresql
データベースとユーザを作成する。
sudo -u postgres createdb <任意のデータベース名> sudo -u postgres createuser <任意のデータベースユーザ名> sudo -u postgres psql # PostgreSQLプロンプト内で実行 ALTER USER <任意のデータベースユーザ名> WITH ENCRYPTED PASSWORD '<パスワード>'; GRANT ALL PRIVILEGES ON DATABASE <任意のデータベース名> TO <任意のデータベースユーザ名>;
MariaDBを使用する場合
MariaDBのインストール
MariaDBをインストールする。
# RHEL sudo dnf install mariadb-server mariadb # SUSE sudo zypper install mariadb mariadb-client # Raspberry Pi sudo apt install mariadb-server mariadb-client
MariaDBサービスを起動して、自動起動を有効にする。
sudo systemctl start mariadb sudo systemctl enable mariadb
MariaDBのセキュリティを設定する。
- rootパスワードの設定
- anonymousユーザの削除
- リモートrootログインの禁止
- テストデータベースの削除
- 権限テーブルの再読み込み
sudo mysql_secure_installation
MariaDBへログインして、データベースの作成、ユーザの作成、権限の設定を行う。
sudo mysql -u root -p # データベースとユーザの作成 CREATE DATABASE <任意のデータベース名>; CREATE USER '<任意のデータベースユーザ名>'@'localhost' IDENTIFIED BY '<データベースユーザのパスワード>'; # 権限の設定 GRANT ALL PRIVILEGES ON <任意のデータベース名>.* TO '<任意のデータベースユーザ名>'@'localhost'; # 設定を反映 FLUSH PRIVILEGES;
MariaDB向けPythonドライバをインストールする場合の例を示す。
pip install mysql-connector-python sqlalchemy mysqlclient # または pip3 install mysql-connector-python sqlalchemy mysqlclient
必要な場合は、MariaDBのパフォーマンスの最適化を行う。
# /etc/my.cnf.d/server.cnfファイル (RHEL/SUSE)
# /etc/mysql/mariadb.conf.d/50-server.cnfファイル (Raspberry Pi)
[mysqld]
# バッファプールサイズ (利用可能メモリの50-70[%]程度)
innodb_buffer_pool_size = 1G
# クエリキャッシュ
query_cache_type = 1
query_cache_size = 128M
# 接続数の設定
max_connections = 150
# スレッドキャッシュ
thread_cache_size = 8
# テーブルオープンキャッシュ
table_open_cache = 2000
# InnoDBの設定
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 2
innodb_log_buffer_size = 16M
バックアップ設定
バックアップスクリプトを作成する。
#!/usr/bin/env sh
BACKUP_DIR="<任意のバックアップディレクトリ 例: /var/backups/mysql>"
BACKUP_FILE="<任意のバックアップファイル名 例: myapi_db>"
DATE=$(date +%Y%m%d_%H%M%S) # 日付を付加する場合
BACKUP_FILE="$BACKUP_DIR/${BACKUP_FILE}_$DATE.sql" # バックアップファイルのパス
# バックアップの作成
# 例: mysqldump -u myapi_user -p myapi_db > $BACKUP_FILE
mysqldump -u <データベースユーザ名> -p<データベースユーザのパスワード> <データベース名> > $BACKUP_FILE
# 圧縮
gzip $BACKUP_FILE
# 30日以上前のバックアップを削除 (オプション)
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
crontabに登録して、定期的にバックアップを実行する。
crontab -e # 毎日午前2時にバックアップを実行 0 2 * * * /path/to/backup_script.sh
MariaDBのメンテナンス
定期的な実行が推奨されるメンテナンスを行う。
-- テーブルの分析
ANALYZE TABLE items;
-- テーブルの最適化
OPTIMIZE TABLE items;
-- インデックスの使用状況確認
SHOW INDEX FROM items;
-- スロークエリの確認
SHOW VARIABLES LIKE '%slow%';
SHOW VARIABLES LIKE '%long%';
Redisを使用する場合
Redisは、キャッシュやセッション管理、キューイング等に使用されるインメモリデータベースである。
HTTP APIサーバのパフォーマンス向上のために、頻繁にアクセスされるデータのキャッシュとして活用できる。
Redisをインストールする。
# RHEL sudo dnf install redis # SUSE sudo zypper install redis # Raspberry Pi sudo apt install redis-server
Redisサービスを起動して、自動起動を有効にする。
sudo systemctl start redis sudo systemctl enable redis
Redis向けPythonクライアントをインストールする場合の例を示す。
pip install redis
APIの基本
Python + Flaskを使用する場合
以下に示すソースコードはサーバサイドであり、APIサーバ内部で動作する部分である。
したがって、クライアントが直接触れる部分ではなく、APIサーバの内部実装として機能する。
クライアントは以下に示すソースコードを意識することなく、定義されたAPIエンドポイントを通じてデータの操作や機能の実行を行うことが可能となる。
# app.pyファイル
from flask import Flask, request, jsonify
from flask_cors import CORS
import logging
app = Flask(__name__)
CORS(app) # クロスオリジンリクエストを許可
# ロギング設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ヘルスチェックエンドポイント
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({'status': 'healthy', 'version': '1.0.0'}), 200
# データ処理エンドポイント
@app.route('/api/process', methods=['POST'])
def process_data():
try:
data = request.get_json()
# 入力バリデーション
if not data or 'input' not in data:
return jsonify({'error': 'Invalid input'}), 400
# 処理ロジック
result = perform_processing(data['input'])
logger.info(f"Processing completed for input: {data['input']}")
return jsonify({'result': result}), 200
except Exception as e:
logger.error(f"Error processing data: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
def perform_processing(input_data):
# 実際の処理ロジックをここに実装
return input_data.upper()
# エラーハンドラ
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({'error': 'Internal server error'}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
データベースを使用する場合の実装例を示す。
# database.pyファイル
# SQLAlchemyを使用したデータベース接続設定
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
# 環境変数からデータベース接続情報を取得
DB_USER = os.getenv('DB_USER', 'myapi_user')
DB_PASSWORD = os.getenv('DB_PASSWORD', 'your_password')
DB_HOST = os.getenv('DB_HOST', 'localhost')
DB_NAME = os.getenv('DB_NAME', 'myapi_db')
SQLALCHEMY_DATABASE_URL = f"mysql+mysqlconnector://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
pool_size=5,
max_overflow=10,
pool_timeout=30,
pool_recycle=1800
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# データベースセッションの取得
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Node.js + Expressを使用する場合
// server.jsファイル
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const helmet = require('helmet');
const app = express();
const PORT = process.env.PORT || 3000;
// ミドルウェアの設定
app.use(helmet()); // セキュリティヘッダーの設定
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// ロギングミドルウェア
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
});
// ヘルスチェックエンドポイント
app.get('/health', (req, res) => {
res.json({ status: 'healthy', version: '1.0.0' });
});
// データ処理エンドポイント
app.post('/api/process', (req, res) => {
try {
const { input } = req.body;
if (!input) {
return res.status(400).json({ error: 'Invalid input' });
}
// 処理ロジック
const result = input.toUpperCase();
res.json({ result });
}
catch (error) {
console.error('Error processing data:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
});
// サーバ起動
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Go言語を使用する場合
// main.goファイル
package main
import (
"net/http"
"log"
"github.com/gin-gonic/gin"
)
type ProcessRequest struct {
Input string `json:"input" binding:"required"`
}
type ProcessResponse struct {
Result string `json:"result"`
}
func main() {
// Ginルータの初期化
router := gin.Default()
// CORSミドルウェアの設定
router.Use(corsMiddleware())
// ヘルスチェックエンドポイント
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"version": "1.0.0",
})
})
// データ処理エンドポイント
router.POST("/api/process", processData)
// サーバ起動
log.Println("Server starting on port 8080")
router.Run(":8080")
}
func processData(c *gin.Context) {
var req ProcessRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
return
}
// 処理ロジック
result := strings.ToUpper(req.Input)
c.JSON(http.StatusOK, ProcessResponse{Result: result})
}
func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
認証と認可の実装
APIキー認証を実装する場合
Python + Flaskでの実装例を示す。
# auth.pyファイル
from flask import request, jsonify
from functools import wraps
import os
# 環境変数からAPIキーを取得
VALID_API_KEYS = os.getenv('API_KEYS', '').split(',')
def require_api_key(f):
@wraps(f)
def decorated_function(*args, **kwargs):
api_key = request.headers.get('X-API-Key')
if not api_key or api_key not in VALID_API_KEYS:
return jsonify({'error': 'Invalid or missing API key'}), 401
return f(*args, **kwargs)
return decorated_function
# 使用例
@app.route('/api/protected', methods=['GET'])
@require_api_key
def protected_endpoint():
return jsonify({'message': 'Access granted'})
JWT認証を実装する場合
必要なライブラリをインストールする。
pip install pyjwt
JWT認証の実装例を示す。
# jwt_auth.pyファイル
import jwt
import datetime
from flask import request, jsonify
from functools import wraps
import os
SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'your-secret-key')
def generate_token(user_id):
payload = {
'user_id': user_id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24),
'iat': datetime.datetime.utcnow()
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return token
def require_jwt(f):
@wraps(f)
def decorated_function(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'error': 'Token is missing'}), 401
try:
# Bearer トークンの形式から実際のトークンを抽出
if token.startswith('Bearer '):
token = token[7:]
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
request.user_id = payload['user_id']
except jwt.ExpiredSignatureError:
return jsonify({'error': 'Token has expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'error': 'Invalid token'}), 401
return f(*args, **kwargs)
return decorated_function
レート制限の実装
レート制限を実装することにより、API呼び出しの過度な使用を防止して、サーバリソースを保護することができる。
必要なライブラリをインストールする。
pip install flask-limiter
以下の例では、レート制限を行っている。
# rate_limit.pyファイル
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
app = Flask(__name__)
# レート制限の設定
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"],
storage_uri="redis://localhost:6379"
)
# 特定のエンドポイントに対するレート制限
@app.route('/api/data')
@limiter.limit("10 per minute")
def get_data():
return jsonify({'data': 'sample data'})
HTTPS対応
Let's Encryptを使用する場合
Certbotをインストールする。
# RHEL sudo dnf install ca-certificates python3-certbot python3-certbot-nginx # SUSE sudo zypper install python3-certbot python3-certbot-nginx # Raspberry Pi sudo apt install certbot python3-certbot-nginx
SSL証明書を取得する。
sudo certbot --nginx -d <ドメイン名 例: api.example.com>
証明書の自動更新を設定する。
# crontabに登録 sudo crontab -e # 例 : 毎日午前3時に更新チェックを実行 0 3 * * * certbot renew --quiet
自己署名証明書を使用する場合
開発環境やテスト環境では、自己署名証明書を使用することができる。
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
Pythonアプリケーションで使用する場合の例を示す。
# HTTPS対応でFlaskアプリを起動
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, ssl_context=('cert.pem', 'key.pem'))
リバースプロキシの設定
Nginxを使用する場合
Nginxをインストールする。
# RHEL sudo dnf install nginx # SUSE sudo zypper install nginx # Raspberry Pi sudo apt install nginx
Nginx設定ファイルを作成する。
# /etc/nginx/sites-available/myapiファイル (Raspberry Pi)
# または /etc/nginx/conf.d/myapi.confファイル (RHEL/SUSE)
upstream api_backend {
server localhost:5000;
# ロードバランシングの場合
# server localhost:5001;
# server localhost:5002;
}
server {
listen 80;
server_name api.example.com;
# HTTPSへリダイレクト
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL証明書の設定
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# SSL設定の最適化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# セキュリティヘッダ
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# ログ設定
access_log /var/log/nginx/api_access.log;
error_log /var/log/nginx/api_error.log;
# プロキシ設定
location / {
proxy_pass http://api_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# タイムアウト設定
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# バッファ設定
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# 静的ファイルの配信 (必要な場合)
location /static/ {
alias /var/www/api/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Raspberry Piの場合は、設定ファイルのシンボリックリンクを作成する。
sudo ln -s /etc/nginx/sites-available/myapi /etc/nginx/sites-enabled/
設定ファイルの文法チェックを行う。
sudo nginx -t
Nginxサービスを起動して、自動起動を有効にする。
sudo systemctl start nginx sudo systemctl enable nginx
設定を変更した場合は、Nginxをリロードする。
sudo systemctl reload nginx
Systemdサービスファイルの作成
Python + Flaskを使用する場合
# /etc/systemd/system/<任意のサービス名>.serviceファイル
[Unit]
Description=HTTP API Server
After=network.target
[Service]
Type=simple
User=<実行する任意のユーザ名>
Group=<実行する任意のグループ名>
WorkingDirectory=<HTTP APIのプロジェクトディレクトリ 例: /home/myapi_user/my-api>
Environment="PATH=<仮想環境のbinディレクトリ 例: /home/myapi_user/my-api/venv/bin>"
ExecStart=<Python実行ファイルのパス> app.py
# 例: ExecStart=/home/myapi_user/my-api/venv/bin/python app.py
Restart=always
RestartSec=10
# セキュリティ設定
PrivateTmp=true
NoNewPrivileges=true
# ログ設定
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapi
[Install]
WantedBy=multi-user.target
Node.js + Expressを使用する場合
# /etc/systemd/system/<任意のサービス名>.serviceファイル
[Unit]
Description=HTTP API Server (Node.js)
After=network.target
[Service]
Type=simple
User=<実行する任意のユーザ名>
Group=<実行する任意のグループ名>
WorkingDirectory=<HTTP APIのプロジェクトディレクトリ 例: /home/myapi_user/my-api>
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000
# セキュリティ設定
PrivateTmp=true
NoNewPrivileges=true
# ログ設定
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapi-node
[Install]
WantedBy=multi-user.target
サービスファイルを作成した後は、systemdデーモンをリロードして、サービスを起動する。
sudo systemctl daemon-reload sudo systemctl start <サービス名> sudo systemctl enable <サービス名>
サービスの状態を確認する。
sudo systemctl status <サービス名>
ログを確認する。
sudo journalctl -u <サービス名> -f
ファイヤーウォールの設定
Firewalldの場合
sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload
特定のポートを開放する場合は、以下のようにする。
sudo firewall-cmd --permanent --add-port=5000/tcp sudo firewall-cmd --reload
設定を確認する。
sudo firewall-cmd --list-all
UFWの場合
sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable
特定のポートを開放する場合は、以下のようにする。
sudo ufw allow 5000/tcp
設定を確認する。
sudo ufw status
監視とログ管理
Prometheusによる監視
PrometheusのGithubにアクセスして、Prometheusをインストールする。
ダウンロードしたファイルを解凍する。
tar xf prometheus-<バージョン>.linux-<アーキテクチャ>.tar.gz cd prometheus-<バージョン>.linux-<アーキテクチャ>
Prometheus設定ファイルを作成する。
# prometheus.ymlファイル
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'api_server'
static_configs:
- targets: ['localhost:5000']
metrics_path: '/metrics'
Pythonアプリケーションにメトリクスエンドポイントを追加する。
pip install prometheus-flask-exporter
# app.pyファイルにメトリクス設定を追加
from prometheus_flask_exporter import PrometheusMetrics
app = Flask(__name__)
metrics = PrometheusMetrics(app)
# カスタムメトリクスの定義も可能
metrics.info('app_info', 'Application info', version='1.0.0')
Grafanaによる可視化
Grafanaをインストールする。
# RHEL sudo dnf install grafana # SUSE sudo zypper install grafana # Raspberry Pi sudo apt install apt-transport-https software-properties-common sudo wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list sudo apt update sudo apt install grafana
Grafanaサービスを起動して、自動起動を有効にする。
sudo systemctl start grafana-server sudo systemctl enable grafana-server
Webブラウザで http://localhost:3000 にアクセスして、Grafanaにログインする。
デフォルトのログイン情報は、ユーザ名: admin、パスワード: admin である。
ログ管理
ログローテーションを設定する。
# /etc/logrotate.d/myapiファイル
/var/log/myapi/*.log {
daily
rotate 30
compress
delaycompress
notifempty
create 0640 myapi_user myapi_group
sharedscripts
postrotate
systemctl reload myapi > /dev/null 2>&1 || true
endscript
}
構造化ログの実装例を示す。
# logging_config.pyファイル
import logging
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
def format(self, record):
log_data = {
'timestamp': datetime.utcnow().isoformat(),
'level': record.levelname,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno
}
if hasattr(record, 'user_id'):
log_data['user_id'] = record.user_id
if record.exc_info:
log_data['exception'] = self.formatException(record.exc_info)
return json.dumps(log_data)
# ロガーの設定
def setup_logger(name):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
handler = logging.FileHandler('/var/log/myapi/app.log')
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
return logger
CI/CD環境の構築
GitLabを使用する場合
# .gitlab-ci.ymlファイル
stages:
- test
- build
- deploy
variables:
PYTHON_VERSION: "3.9"
test:
stage: test
image: python:${PYTHON_VERSION}
script:
- pip install -r requirements.txt
- pip install pytest pytest-cov
- pytest --cov=app tests/
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
build:
stage: build
script:
- docker build -t myapi:${CI_COMMIT_SHORT_SHA} .
- docker tag myapi:${CI_COMMIT_SHORT_SHA} myapi:latest
only:
- main
deploy:
stage: deploy
script:
- ssh user@yourserver 'cd /path/to/api && git pull origin main'
- ssh user@yourserver 'cd /path/to/api && source venv/bin/activate && pip install -r requirements.txt'
- ssh user@yourserver 'sudo systemctl restart myapi'
only:
- main
when: manual
GitHub Actionsを使用する場合
# .github/workflows/deploy.ymlファイル
name: Deploy API
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest --cov=app tests/
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /path/to/api
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
sudo systemctl restart myapi
セキュリティ対策
入力バリデーション
# validation.pyファイル
from flask import request, jsonify
from functools import wraps
import re
def validate_input(schema):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
data = request.get_json()
for field, rules in schema.items():
if rules.get('required') and field not in data:
return jsonify({'error': f'Missing required field: {field}'}), 400
if field in data:
value = data[field]
# 型チェック
if 'type' in rules and not isinstance(value, rules['type']):
return jsonify({'error': f'Invalid type for field: {field}'}), 400
# 最大長チェック
if 'max_length' in rules and len(str(value)) > rules['max_length']:
return jsonify({'error': f'Field {field} exceeds maximum length'}), 400
# 正規表現チェック
if 'pattern' in rules and not re.match(rules['pattern'], str(value)):
return jsonify({'error': f'Field {field} does not match required pattern'}), 400
return f(*args, **kwargs)
return decorated_function
return decorator
# 使用例
@app.route('/api/user', methods=['POST'])
@validate_input({
'username': {'required': True, 'type': str, 'max_length': 50, 'pattern': r'^[a-zA-Z0-9_]+$'},
'email': {'required': True, 'type': str, 'pattern': r'^[\w\.-]+@[\w\.-]+\.\w+$'}
})
def create_user():
# 処理ロジック
pass
SQLインジェクション対策
SQLAlchemyを使用することにより、パラメータ化されたクエリが自動的に生成されて、SQLインジェクションを防ぐことができる。
# 安全な実装例
from sqlalchemy import text
# 悪い例 (脆弱)
# query = f"SELECT * FROM users WHERE username = '{username}'"
# 良い例 (安全)
query = text("SELECT * FROM users WHERE username = :username")
result = db.session.execute(query, {"username": username})
CORS設定
# cors_config.pyファイル
from flask_cors import CORS
# 本番環境向けの厳格なCORS設定
CORS(app, resources={
r"/api/*": {
"origins": ["https://example.com", "https://app.example.com"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"],
"expose_headers": ["Content-Range", "X-Content-Range"],
"supports_credentials": True,
"max_age": 3600
}
})