サーバ - HTTP API

2026年1月3日 (土) 11:27時点におけるWiki (トーク | 投稿記録)による版 (ページの作成:「== 概要 == HTTP APIは、HTTPプロトコルを使用して、クライアントとサーバ間でデータのやり取りを行うインターフェースである。<br> HTTPメソッド (GET、POST、PUT、DELETE等) を使用して、サーバ上の機能やデータへのアクセスを実現する。<br> <br> HTTP APIは、必ずしもRESTの設計原則に従う必要はなく、RPC形式やカスタムエンドポイント設計等、柔軟なアプロー…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

概要

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
    }
 })