본문 바로가기

리눅스

라라벨 개발 환경을 도커 컨테이너로 구성하는 방법

반응형

라라벨 개발 환경을 도커 컨테이너로 구성하는 방법

테스트 환경

nginx 버전 정보

docker compose exec nginx nginx -v

php 버전 정보

docker compose exec php-fpm php --version

composer 버전 정보

docker compose exec php-fpm composer --version

라라벨 artisan 버전 정보

docker compose exec php-fpm php artisan --version

작업 디렉토리 이동

mkdir -p /apps/container/docker-laravel-development-environment
cd /apps/container/docker-laravel-development-environment

라라벨 개발 환경에 필요한 파일 생성

Docker Compose 파일

더보기

---

cat > docker-compose.yml << 'EOF'
# docker-compose.yml
services:

  nginx:
    image: nginx:1.29-alpine
    container_name: nginx
    hostname: nginx
    volumes:
      - ./html:/var/www/html
      - ./docker/nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - php-fpm
    ports:
      - "80:80"
    networks:
      - laravel-network

  php-fpm:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    container_name: php-fpm
    hostname: php-fpm
    environment:
      - DB_HOST=mysql
      - DB_DATABASE=laravel
      - DB_USERNAME=laravel
      - DB_PASSWORD=secret
    volumes:
      - ./html:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
    networks:
      - laravel-network
    healthcheck:
      test: ["CMD", "php", "-r", "echo 'OK';"]
      interval: 30s
      timeout: 10s
      retries: 3

  mysql:
    image: mysql:8.4
    container_name: mysql
    hostname: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: secret
    ports:
      - "3306:3306"
    networks:
      - laravel-network

networks:
  laravel-network:
    driver: bridge
EOF
vim vim docker-compose.yml
services:

  nginx:
    image: nginx:1.29-alpine
    container_name: nginx
    hostname: nginx
    volumes:
      - ./html:/var/www/html
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./docker/nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - php-fpm
    ports:
      - "80:80"
    networks:
      - laravel-network

  php-fpm:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    image: anti1346/php-fpm:8.3
    container_name: php-fpm
    hostname: php-fpm
    environment:
      - DB_HOST=mysql
      - DB_DATABASE=laravel
      - DB_USERNAME=laravel
      - DB_PASSWORD=secret
    volumes:
      - ./html:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
    depends_on:
      mysql:
        condition: service_healthy
      redis-cluster-init:
        condition: service_started
    networks:
      - laravel-network
    healthcheck:
      test: ["CMD", "php", "-r", "echo 'OK';"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  mysql:
    image: mysql:8.4
    container_name: mysql
    hostname: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: secret
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - laravel-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

  ####### Predixy Proxy #######
  predixy:
    image: anti1346/predixy:1.0.5
    container_name: predixy
    hostname: predixy
    volumes:
      - ./docker/predixy/conf/predixy.conf:/etc/predixy/conf/predixy.conf
    ports:
      - "6379:7617"
    networks:
      - laravel-network

  ####### Redis Cluster Nodes #######
  redis-node-1:
    image: redis:8.4
    container_name: redis-node-1
    hostname: redis-node-1
    command: >
      redis-server
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --appendonly yes
      --requirepass redis_password
      --masterauth redis_password
    ports:
      - "7001:6379"
    volumes:
      - redis-data-1:/data
    networks:
      - laravel-network

  redis-node-2:
    image: redis:8.4
    container_name: redis-node-2
    hostname: redis-node-2
    command: >
      redis-server
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --appendonly yes
      --requirepass redis_password
      --masterauth redis_password
    ports:
      - "7002:6379"
    volumes:
      - redis-data-2:/data
    networks:
      - laravel-network

  redis-node-3:
    image: redis:8.4
    container_name: redis-node-3
    hostname: redis-node-3
    command: >
      redis-server
      --cluster-enabled yes
      --cluster-config-file nodes.conf
      --cluster-node-timeout 5000
      --appendonly yes
      --requirepass redis_password
      --masterauth redis_password
    ports:
      - "7003:6379"
    volumes:
      - redis-data-3:/data
    networks:
      - laravel-network

  # Run cluster initialization automatically
  redis-cluster-init:
    image: redis:8.4
    container_name: redis-cluster-init
    depends_on:
      - redis-node-1
      - redis-node-2
      - redis-node-3
    entrypoint: >
      sh -c "
      sleep 5 &&
      echo 'yes' | redis-cli --cluster create
      redis-node-1:6379 redis-node-2:6379 redis-node-3:6379
      --cluster-replicas 0
      -a redis_password
      "
    networks:
      - laravel-network

volumes:
  mysql-data:
  redis-data-1:
  redis-data-2:
  redis-data-3:

networks:
  laravel-network:
    driver: bridge

---

PHP-FPM Dockerfile

mkdir -p docker/php
더보기

---

cat > docker/php/Dockerfile << 'EOF'
# docker/php/Dockerfile
FROM php:8.3-fpm-alpine

# 1. 시스템 패키지 설치
RUN apk update && apk add --no-cache \
    # 기본 도구
    curl git unzip vim \
    # PHP 확장 의존성
    libpng-dev libjpeg-turbo-dev libwebp-dev freetype-dev \
    libxml2-dev oniguruma-dev libzip-dev postgresql-dev \
    # Redis 빌드 도구(일시적)
    autoconf g++ make

# 2. PHP 확장 설치
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp && \
    docker-php-ext-install -j$(nproc) \
    pdo_mysql mbstring xml zip bcmath pcntl gd

# 3. Redis 확장 설치
RUN pecl install redis && docker-php-ext-enable redis

# 4. 빌드 도구 정리 (이미지 용량 최적화)
RUN apk del autoconf g++ make && \
    rm -rf /tmp/pear && \
    docker-php-source delete

# 5. Composer 설치
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# 6. 작업 디렉토리 및 권한 설정
WORKDIR /var/www/html

# 7.권한 설정
RUN chmod -R 775 /var/www/html

# PHP-FPM 사용자 생성 및 권한 설정
#RUN chown -R www-data:www-data /var/www/html
#USER www-data

# 8. health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
    CMD curl -f http://localhost/ping || exit 1
EOF

---

PHP 설정 파일

더보기

---

cat > docker/php/php.ini << 'EOF'
# docker/php/php.ini
memory_limit = 512M
upload_max_filesize = 100M
post_max_size = 100M
max_execution_time = 300
max_input_time = 300

; 에러 리포트
display_errors = On
error_reporting = E_ALL

; OPcache (성능 향상)
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
EOF

---

Nginx 설정

mkdir -p docker/nginx/conf.d
더보기

---

cat > docker/nginx/nginx.conf << 'EOF'
# docker/nginx/nginx.conf
user www-data www-data;

worker_processes auto;

error_log /var/log/nginx/error.log notice;

pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;
    
    server_tokens off;

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;
    gzip on;

    include /etc/nginx/conf.d/*.conf;
}
EOF

---

Nginx 가상 호스트 설정

더보기

---

cat > docker/nginx/conf.d/default.conf << 'EOF'
# docker/nginx/conf.d/default.conf
server {
    listen 80;
    server_name localhost;
    root /var/www/html/public;
    index index.php index.html;

    error_log /var/log/nginx/default-error.log;
    access_log /var/log/nginx/default-access.log;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass php-fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        
        fastcgi_read_timeout 300;
        fastcgi_send_timeout 300;
    }

    location ~ /\.ht {
        deny all;
    }
}
EOF

---

predixy 설정

mkdir -p docker/predixy/conf
더보기

---

cat > docker/predixy/conf/predixy.conf << 'EOF'
# docker/predixy/conf/predixy.conf
Name PredixyExample
Bind 0.0.0.0:7617
ClientTimeout 300

LogVerbSample 0
LogDebugSample 0
LogInfoSample 10000
LogNoticeSample 1
LogWarnSample 1
LogErrorSample 1

Authority {
    Auth {
        Mode write
    }
    Auth "#a complex password#" {
        Mode admin
    }
}

ClusterServerPool {
    Password my_redis_password
    MasterReadPriority 60
   StaticSlaveReadPriority 50
    DynamicSlaveReadPriority 50
    RefreshInterval 1
    ServerTimeout 1
   ServerFailureLimit 10
    ServerRetryTimeout 1
    KeepAlive 120
    Servers {
        + redis-node-1:6379
        + redis-node-2:6379
        + redis-node-3:6379
    }
}

Include latency.conf
EOF

---

Docker 컨테이너 빌드 및 실행

docker compose down -v
docker compose build --no-cache php-fpm
docker compose up -d

Nginx 정보 확인

docker compose exec nginx nginx -v

PHP 정보 확인

docker compose exec php-fpm php -v
docker compose exec php-fpm sh -c "php -m | egrep 'redis|gd'"

Nginx 테스트

docker compose exec nginx mkdir -p /var/www/html/public
docker compose exec nginx sh -c "echo 'Container Nginx Server' > /var/www/html/public/index.html"

Nginx 기본 페이지 접속 테스트

curl -I http://localhost
728x90

라라벨 프로젝트 생성

public 디렉토리 삭제

docker compose exec nginx rm -rf /var/www/html/public

라라벨 프로젝트 생성

docker compose exec php-fpm composer create-project laravel/laravel . --prefer-dist --no-interaction

라라벨 파일 확인

docker compose exec php-fpm ls -la /var/www/html

환경 설정

docker compose exec php-fpm cp .env.example .env

.env 편집

vim html/.env
# --- MySQL 설정 ---
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret

# --- Redis 설정 (Predixy Proxy를 통해 클러스터 연결) ---
REDIS_CLIENT=predis
REDIS_HOST=predixy
REDIS_PASSWORD=redis_password
REDIS_PORT=7617
REDIS_CLUSTER=redis

CACHE_STORE=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

APP_KEY 생성

docker compose exec php-fpm php artisan key:generate

APP_KEY 확인

docker compose exec php-fpm grep APP_KEY .env

스토리지 링크 및 권한 설정

  • 저장소 권한 설정
docker compose exec php-fpm chmod -R 775 storage bootstrap/cache
  • 소유권 설정
docker compose exec php-fpm chown -R www-data:www-data storage
docker compose exec php-fpm chown -R www-data:www-data bootstrap/cache
docker compose exec php-fpm php artisan migrate

캐시 클리어

docker compose exec php-fpm php artisan config:clear
docker compose exec php-fpm php artisan cache:clear
docker compose exec php-fpm php artisan config:cache

페이지 접속 테스트

curl -s -o /dev/null -w "%{http_code}\n" -I http://localhost

Hello World 페이지 접속 테스트

라우트 파일 생성

docker compose exec php-fpm sh -c 'cat > routes/web.php << "EOF"
<?php
Route::get("/", function() { return "Laravel, Hello World!"; });
EOF'

파일 문법 검사

docker compose exec php-fpm php -l routes/web.php

라우트 목록 확인

docker compose exec php-fpm php artisan route:list

페이지 접속 테스트

curl http://localhost
Laravel, Hello World!

MySQL, Redis 테스트 코드

Predis 패키지 설치

docker compose exec -it php-fpm composer require predis/predis

Post 모델과 마이그레이션 파일 생성

docker compose exec -it php-fpm php artisan make:model Post -m

환경 설정 파일

vim html/.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret

REDIS_CLIENT=phpredis
REDIS_CLUSTER=redis
REDIS_HOST=predixy
REDIS_PORT=7617
REDIS_PASSWORD=redis_password
REDIS_DB=0

Redis 설정 확인

vim html/config/database.php

컨트롤러 업데이트

vim html/app/Http/Controllers/TestController.php
더보기

---

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class TestController extends Controller
{
    public function test()
    {
        return view('test');
    }

    public function testDatabase()
    {
        try {
            // 테스트 테이블 생성 (없는 경우)
            DB::statement('CREATE TABLE IF NOT EXISTS test_table (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(255),
                value TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            )');

            // 데이터 삽입
            $id = DB::table('test_table')->insertGetId([
                'name' => 'Test ' . now()->format('Y-m-d H:i:s'),
                'value' => 'This is a test value from Laravel',
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            // 데이터 조회
            $data = DB::table('test_table')->orderBy('id', 'desc')->limit(10)->get();

            return response()->json([
                'success' => true,
                'message' => 'Database test completed successfully',
                'inserted_id' => $id,
                'recent_data' => $data,
                'total_records' => DB::table('test_table')->count()
            ]);

        } catch (\Exception $e) {
            Log::error('Database test failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Database test failed: ' . $e->getMessage()
            ], 500);
        }
    }

    public function testRedis()
    {
        try {
            $timestamp = now()->format('Y-m-d H:i:s');
            $key = 'laravel_test:' . $timestamp;
            $value = 'Hello from Laravel at ' . $timestamp;

            // Redis에 데이터 쓰기
            Redis::set($key, $value);
            
            // Redis에서 데이터 읽기
            $retrievedValue = Redis::get($key);

            // 추가 테스트: 해시 데이터
            $hashKey = 'user:test:' . $timestamp;
            Redis::hset($hashKey, 'name', 'Test User');
            Redis::hset($hashKey, 'email', 'test@example.com');
            Redis::hset($hashKey, 'created_at', $timestamp);
            
            $userData = Redis::hgetall($hashKey);

            return response()->json([
                'success' => true,
                'message' => 'Redis test completed successfully',
                'data' => [
                    'key' => $key,
                    'set_value' => $value,
                    'retrieved_value' => $retrievedValue,
                    'user_data' => $userData
                ]
            ]);

        } catch (\Exception $e) {
            Log::error('Redis test failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Redis test failed: ' . $e->getMessage()
            ], 500);
        }
    }

    public function testRedisCluster()
    {
        try {
            $timestamp = now()->format('Y-m-d H:i:s');
            
            // 여러 키에 데이터 쓰기 (다른 슬롯에 분산될 수 있도록)
            $keys = [];
            for ($i = 1; $i <= 5; $i++) {
                $key = "cluster_test:{$i}:" . $timestamp;
                $value = "Cluster test value {$i} at " . $timestamp;
                
                Redis::connection()->set($key, $value);
                $keys[] = $key;
            }

            // 모든 키에서 데이터 읽기
            $retrievedData = [];
            foreach ($keys as $key) {
                $retrievedData[$key] = Redis::connection()->get($key);
            }

            return response()->json([
                'success' => true,
                'message' => 'Redis cluster test completed successfully',
                'data' => [
                    'keys_written' => $keys,
                    'retrieved_data' => $retrievedData,
                ]
            ]);

        } catch (\Exception $e) {
            Log::error('Redis cluster test failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Redis cluster test failed: ' . $e->getMessage()
            ], 500);
        }
    }

    // 사용자 입력을 받아 데이터베이스에 저장
    public function saveToDatabase(Request $request)
    {
        try {
            $request->validate([
                'name' => 'required|string|max:255',
                'value' => 'required|string',
            ]);

            // 데이터 삽입
            $id = DB::table('test_table')->insertGetId([
                'name' => $request->name,
                'value' => $request->value,
                'created_at' => now(),
                'updated_at' => now(),
            ]);

            // 최근 데이터 조회
            $recentData = DB::table('test_table')->orderBy('id', 'desc')->limit(10)->get();

            return response()->json([
                'success' => true,
                'message' => 'Data saved to database successfully',
                'inserted_id' => $id,
                'data' => [
                    'id' => $id,
                    'name' => $request->name,
                    'value' => $request->value
                ],
                'recent_data' => $recentData,
                'total_records' => DB::table('test_table')->count()
            ]);

        } catch (\Exception $e) {
            Log::error('Save to database failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to save data: ' . $e->getMessage()
            ], 500);
        }
    }

    // 사용자 입력을 받아 Redis에 저장
    public function saveToRedis(Request $request)
    {
        try {
            $request->validate([
                'key' => 'required|string|max:255',
                'value' => 'required|string',
            ]);

            $timestamp = now()->format('Y-m-d H:i:s');
            $key = $request->key . ':' . $timestamp;

            // Redis에 데이터 쓰기
            Redis::set($key, $request->value);
            
            // Redis에서 데이터 읽기
            $retrievedValue = Redis::get($key);

            return response()->json([
                'success' => true,
                'message' => 'Data saved to Redis successfully',
                'data' => [
                    'key' => $key,
                    'set_value' => $request->value,
                    'retrieved_value' => $retrievedValue,
                    'timestamp' => $timestamp
                ]
            ]);

        } catch (\Exception $e) {
            Log::error('Save to Redis failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to save data to Redis: ' . $e->getMessage()
            ], 500);
        }
    }

    // 사용자 입력을 받아 Redis 클러스터에 저장
    public function saveToRedisCluster(Request $request)
    {
        try {
            $request->validate([
                'prefix' => 'required|string|max:100',
                'value' => 'required|string',
                'count' => 'required|integer|min:1|max:10'
            ]);

            $timestamp = now()->format('Y-m-d H:i:s');
            $results = [];

            // 여러 키에 데이터 쓰기 (클러스터 분산 테스트)
            for ($i = 1; $i <= $request->count; $i++) {
                $key = $request->prefix . ":{$i}:" . $timestamp;
                $value = $request->value . " - variation {$i}";
                
                Redis::set($key, $value);
                $retrievedValue = Redis::get($key);
                
                $results[] = [
                    'key' => $key,
                    'set_value' => $value,
                    'retrieved_value' => $retrievedValue
                ];
            }

            return response()->json([
                'success' => true,
                'message' => 'Data saved to Redis cluster successfully',
                'data' => [
                    'results' => $results,
                    'total_written' => count($results),
                    'timestamp' => $timestamp
                ]
            ]);

        } catch (\Exception $e) {
            Log::error('Save to Redis cluster failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to save data to Redis cluster: ' . $e->getMessage()
            ], 500);
        }
    }

    // 데이터베이스에서 모든 데이터 조회
    public function getAllDatabaseData()
    {
        try {
            $data = DB::table('test_table')->orderBy('id', 'desc')->get();

            return response()->json([
                'success' => true,
                'data' => $data,
                'total_records' => count($data)
            ]);

        } catch (\Exception $e) {
            Log::error('Get database data failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to retrieve data: ' . $e->getMessage()
            ], 500);
        }
    }

    // Redis에서 패턴으로 데이터 조회
    public function getRedisData(Request $request)
    {
        try {
            $pattern = $request->get('pattern', '*');
            
            // 주의: KEYS 명령은 프로덕션에서 사용하지 마세요. 여기서는 테스트용으로만 사용합니다.
            $keys = Redis::keys($pattern);
            $data = [];
            
            foreach ($keys as $key) {
                $value = Redis::get($key);
                $data[$key] = $value;
            }

            return response()->json([
                'success' => true,
                'data' => $data,
                'total_keys' => count($keys)
            ]);

        } catch (\Exception $e) {
            Log::error('Get Redis data failed: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Failed to retrieve Redis data: ' . $e->getMessage()
            ], 500);
        }
    }
}

---

라우트 업데이트

vim html/routes/web.php
더보기

---

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TestController;

Route::get('/test', [TestController::class, 'test']);
Route::get('/test/db', [TestController::class, 'testDatabase']);
Route::get('/test/redis', [TestController::class, 'testRedis']);
Route::get('/test/redis-cluster', [TestController::class, 'testRedisCluster']);

// 새로운 사용자 입력 라우트
Route::post('/save/db', [TestController::class, 'saveToDatabase']);
Route::post('/save/redis', [TestController::class, 'saveToRedis']);
Route::post('/save/redis-cluster', [TestController::class, 'saveToRedisCluster']);
Route::get('/data/db', [TestController::class, 'getAllDatabaseData']);
Route::get('/data/redis', [TestController::class, 'getRedisData']);

---

뷰 파일 업데이트

vim html/resources/views/test.blade.php
더보기

---

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Laravel Docker Environment Test</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen py-8">
    <div class="container mx-auto px-4 max-w-6xl">
        <h1 class="text-3xl font-bold text-center text-gray-800 mb-8">
             Laravel Docker Environment Test
        </h1>

        <!-- 데이터 입력 섹션 -->
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
            <!-- Database 입력 -->
            <div class="bg-white rounded-lg shadow-md p-6">
                <h2 class="text-xl font-semibold text-gray-700 mb-4">️ Save to Database</h2>
                <form id="db-form" class="space-y-4">
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">Name</label>
                        <input type="text" name="name" required 
                               class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                               placeholder="Enter name">
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">Value</label>
                        <textarea name="value" required rows="3"
                                  class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                                  placeholder="Enter value"></textarea>
                    </div>
                    <button type="submit" 
                            class="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition duration-200">
                        Save to Database
                    </button>
                </form>
                <div id="db-save-result" class="mt-4 p-4 bg-gray-50 rounded hidden"></div>
            </div>

            <!-- Redis 입력 -->
            <div class="bg-white rounded-lg shadow-md p-6">
                <h2 class="text-xl font-semibold text-gray-700 mb-4"> Save to Redis</h2>
                <form id="redis-form" class="space-y-4">
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">Key Prefix</label>
                        <input type="text" name="key" required 
                               class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500"
                               placeholder="Enter key prefix">
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">Value</label>
                        <textarea name="value" required rows="3"
                                  class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500"
                                  placeholder="Enter value"></textarea>
                    </div>
                    <button type="submit" 
                            class="w-full bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4 rounded transition duration-200">
                        Save to Redis
                    </button>
                </form>
                <div id="redis-save-result" class="mt-4 p-4 bg-gray-50 rounded hidden"></div>
            </div>

            <!-- Redis Cluster 입력 -->
            <div class="bg-white rounded-lg shadow-md p-6">
                <h2 class="text-xl font-semibold text-gray-700 mb-4"> Save to Redis Cluster</h2>
                <form id="redis-cluster-form" class="space-y-4">
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">Key Prefix</label>
                        <input type="text" name="prefix" required 
                               class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
                               placeholder="Enter key prefix">
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">Value</label>
                        <textarea name="value" required rows="2"
                                  class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"
                                  placeholder="Enter value"></textarea>
                    </div>
                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">Number of Keys</label>
                        <select name="count" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500">
                            <option value="1">1</option>
                            <option value="3" selected>3</option>
                            <option value="5">5</option>
                            <option value="10">10</option>
                        </select>
                    </div>
                    <button type="submit" 
                            class="w-full bg-purple-500 hover:bg-purple-600 text-white font-medium py-2 px-4 rounded transition duration-200">
                        Save to Cluster
                    </button>
                </form>
                <div id="redis-cluster-save-result" class="mt-4 p-4 bg-gray-50 rounded hidden"></div>
            </div>
        </div>

        <!-- 테스트 버튼 섹션 -->
        <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
            <div class="bg-white rounded-lg shadow-md p-6 text-center">
                <button onclick="testDatabase()" 
                        class="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-4 rounded transition duration-200">
                    ️ Test Database
                </button>
                <div id="db-test-result" class="mt-4 p-4 bg-gray-50 rounded hidden"></div>
            </div>

            <div class="bg-white rounded-lg shadow-md p-6 text-center">
                <button onclick="testRedis()" 
                        class="w-full bg-red-500 hover:bg-red-600 text-white font-medium py-3 px-4 rounded transition duration-200">
                     Test Redis
                </button>
                <div id="redis-test-result" class="mt-4 p-4 bg-gray-50 rounded hidden"></div>
            </div>

            <div class="bg-white rounded-lg shadow-md p-6 text-center">
                <button onclick="testRedisCluster()" 
                        class="w-full bg-purple-500 hover:bg-purple-600 text-white font-medium py-3 px-4 rounded transition duration-200">
                     Test Redis Cluster
                </button>
                <div id="redis-cluster-test-result" class="mt-4 p-4 bg-gray-50 rounded hidden"></div>
            </div>
        </div>

        <!-- 데이터 조회 섹션 -->
        <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
            <div class="bg-white rounded-lg shadow-md p-6">
                <div class="flex justify-between items-center mb-4">
                    <h2 class="text-xl font-semibold text-gray-700">️ Database Data</h2>
                    <button onclick="loadDatabaseData()" 
                            class="bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium py-1 px-3 rounded transition duration-200">
                        Refresh
                    </button>
                </div>
                <div id="db-data" class="max-h-80 overflow-y-auto">
                    <div class="text-gray-500 text-center py-4">No data loaded</div>
                </div>
            </div>

            <div class="bg-white rounded-lg shadow-md p-6">
                <div class="flex justify-between items-center mb-4">
                    <h2 class="text-xl font-semibold text-gray-700"> Redis Data</h2>
                    <button onclick="loadRedisData()" 
                            class="bg-red-500 hover:bg-red-600 text-white text-sm font-medium py-1 px-3 rounded transition duration-200">
                        Refresh
                    </button>
                </div>
                <div id="redis-data" class="max-h-80 overflow-y-auto">
                    <div class="text-gray-500 text-center py-4">No data loaded</div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // 폼 제출 이벤트 처리
        document.getElementById('db-form').addEventListener('submit', function(e) {
            e.preventDefault();
            saveToDatabase();
        });

        document.getElementById('redis-form').addEventListener('submit', function(e) {
            e.preventDefault();
            saveToRedis();
        });

        document.getElementById('redis-cluster-form').addEventListener('submit', function(e) {
            e.preventDefault();
            saveToRedisCluster();
        });

        function saveToDatabase() {
            const formData = new FormData(document.getElementById('db-form'));
            const resultDiv = document.getElementById('db-save-result');
            
            resultDiv.innerHTML = '<div class="text-blue-500">Saving to database...</div>';
            resultDiv.classList.remove('hidden');

            fetch('/save/db', {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    name: formData.get('name'),
                    value: formData.get('value')
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    resultDiv.innerHTML = `
                        <div class="text-green-600 font-medium">✅ Data saved successfully! (ID: ${data.inserted_id})</div>
                        <div class="mt-2 text-sm">
                            <p><strong>Name:</strong> ${data.data.name}</p>
                            <p><strong>Value:</strong> ${data.data.value}</p>
                            <p><strong>Total Records:</strong> ${data.total_records}</p>
                        </div>
                    `;
                    document.getElementById('db-form').reset();
                    loadDatabaseData(); // 데이터 새로고침
                } else {
                    resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Failed to save: ${data.message}</div>`;
                }
            })
            .catch(error => {
                resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Request failed: ${error}</div>`;
            });
        }

        function saveToRedis() {
            const formData = new FormData(document.getElementById('redis-form'));
            const resultDiv = document.getElementById('redis-save-result');
            
            resultDiv.innerHTML = '<div class="text-blue-500">Saving to Redis...</div>';
            resultDiv.classList.remove('hidden');

            fetch('/save/redis', {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    key: formData.get('key'),
                    value: formData.get('value')
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    resultDiv.innerHTML = `
                        <div class="text-green-600 font-medium">✅ Data saved to Redis successfully!</div>
                        <div class="mt-2 text-sm">
                            <p><strong>Key:</strong> ${data.data.key}</p>
                            <p><strong>Value:</strong> ${data.data.retrieved_value}</p>
                        </div>
                    `;
                    document.getElementById('redis-form').reset();
                    loadRedisData(); // 데이터 새로고침
                } else {
                    resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Failed to save: ${data.message}</div>`;
                }
            })
            .catch(error => {
                resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Request failed: ${error}</div>`;
            });
        }

        function saveToRedisCluster() {
            const formData = new FormData(document.getElementById('redis-cluster-form'));
            const resultDiv = document.getElementById('redis-cluster-save-result');
            
            resultDiv.innerHTML = '<div class="text-blue-500">Saving to Redis cluster...</div>';
            resultDiv.classList.remove('hidden');

            fetch('/save/redis-cluster', {
                method: 'POST',
                headers: {
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    prefix: formData.get('prefix'),
                    value: formData.get('value'),
                    count: parseInt(formData.get('count'))
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    let resultsHtml = '';
                    data.data.results.forEach((result, index) => {
                        resultsHtml += `
                            <div class="mb-2 p-2 bg-white rounded border">
                                <p class="text-xs font-medium">Key ${index + 1}: ${result.key}</p>
                                <p class="text-xs">Value: ${result.retrieved_value}</p>
                            </div>
                        `;
                    });

                    resultDiv.innerHTML = `
                        <div class="text-green-600 font-medium">✅ Data saved to Redis cluster successfully!</div>
                        <div class="mt-2 text-sm">
                            <p><strong>Total Keys Written:</strong> ${data.data.total_written}</p>
                            ${resultsHtml}
                        </div>
                    `;
                    document.getElementById('redis-cluster-form').reset();
                    loadRedisData(); // 데이터 새로고침
                } else {
                    resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Failed to save: ${data.message}</div>`;
                }
            })
            .catch(error => {
                resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Request failed: ${error}</div>`;
            });
        }

        // 기존 테스트 함수들
        function testDatabase() {
            const resultDiv = document.getElementById('db-test-result');
            resultDiv.innerHTML = '<div class="text-blue-500">Testing database connection...</div>';
            resultDiv.classList.remove('hidden');

            fetch('/test/db')
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        resultDiv.innerHTML = `
                            <div class="text-green-600 font-medium">✅ Database test successful!</div>
                            <div class="mt-2 text-sm">
                                <p><strong>Inserted ID:</strong> ${data.inserted_id}</p>
                                <p><strong>Total Records:</strong> ${data.total_records}</p>
                            </div>
                        `;
                        loadDatabaseData();
                    } else {
                        resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Database test failed: ${data.message}</div>`;
                    }
                })
                .catch(error => {
                    resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Request failed: ${error}</div>`;
                });
        }

        function testRedis() {
            const resultDiv = document.getElementById('redis-test-result');
            resultDiv.innerHTML = '<div class="text-blue-500">Testing Redis connection...</div>';
            resultDiv.classList.remove('hidden');

            fetch('/test/redis')
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        resultDiv.innerHTML = `
                            <div class="text-green-600 font-medium">✅ Redis test successful!</div>
                            <div class="mt-2 text-sm">
                                <p><strong>Key:</strong> ${data.data.key}</p>
                                <p><strong>Value:</strong> ${data.data.retrieved_value}</p>
                            </div>
                        `;
                        loadRedisData();
                    } else {
                        resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Redis test failed: ${data.message}</div>`;
                    }
                })
                .catch(error => {
                    resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Request failed: ${error}</div>`;
                });
        }

        function testRedisCluster() {
            const resultDiv = document.getElementById('redis-cluster-test-result');
            resultDiv.innerHTML = '<div class="text-blue-500">Testing Redis cluster connection...</div>';
            resultDiv.classList.remove('hidden');

            fetch('/test/redis-cluster')
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        resultDiv.innerHTML = `
                            <div class="text-green-600 font-medium">✅ Redis cluster test successful!</div>
                            <div class="mt-2 text-sm">
                                <p><strong>Keys Written:</strong> ${data.data.keys_written.length}</p>
                            </div>
                        `;
                        loadRedisData();
                    } else {
                        resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Redis cluster test failed: ${data.message}</div>`;
                    }
                })
                .catch(error => {
                    resultDiv.innerHTML = `<div class="text-red-600 font-medium">❌ Request failed: ${error}</div>`;
                });
        }

        function loadDatabaseData() {
            const dataDiv = document.getElementById('db-data');
            dataDiv.innerHTML = '<div class="text-blue-500 text-center py-4">Loading database data...</div>';

            fetch('/data/db')
                .then(response => response.json())
                .then(data => {
                    if (data.success && data.data.length > 0) {
                        let html = '<div class="space-y-2">';
                        data.data.forEach(item => {
                            html += `
                                <div class="p-3 bg-gray-50 rounded border">
                                    <div class="flex justify-between items-start">
                                        <span class="font-medium">#${item.id} - ${item.name}</span>
                                        <span class="text-xs text-gray-500">${new Date(item.created_at).toLocaleString()}</span>
                                    </div>
                                    <p class="text-sm mt-1">${item.value}</p>
                                </div>
                            `;
                        });
                        html += '</div>';
                        dataDiv.innerHTML = html;
                    } else {
                        dataDiv.innerHTML = '<div class="text-gray-500 text-center py-4">No data in database</div>';
                    }
                })
                .catch(error => {
                    dataDiv.innerHTML = `<div class="text-red-500 text-center py-4">Failed to load data: ${error}</div>`;
                });
        }

        function loadRedisData() {
            const dataDiv = document.getElementById('redis-data');
            dataDiv.innerHTML = '<div class="text-blue-500 text-center py-4">Loading Redis data...</div>';

            fetch('/data/redis?pattern=*')
                .then(response => response.json())
                .then(data => {
                    if (data.success && data.total_keys > 0) {
                        let html = '<div class="space-y-2">';
                        Object.entries(data.data).forEach(([key, value]) => {
                            html += `
                                <div class="p-3 bg-gray-50 rounded border">
                                    <div class="font-medium text-sm break-all">${key}</div>
                                    <p class="text-sm mt-1 text-gray-700">${value}</p>
                                </div>
                            `;
                        });
                        html += '</div>';
                        dataDiv.innerHTML = html;
                    } else {
                        dataDiv.innerHTML = '<div class="text-gray-500 text-center py-4">No data in Redis</div>';
                    }
                })
                .catch(error => {
                    dataDiv.innerHTML = `<div class="text-red-500 text-center py-4">Failed to load data: ${error}</div>`;
                });
        }

        // 페이지 로드 시 데이터 불러오기
        document.addEventListener('DOMContentLoaded', function() {
            loadDatabaseData();
            loadRedisData();
        });
    </script>
</body>
</html>

---

PHP 문법 검사

docker compose exec php-fpm php -l routes/web.php

라우트 캐시 클리어

docker compose exec php-fpm php artisan route:clear

라우트 목록 확인

docker compose exec php-fpm php artisan route:list

캐시 클리어

docker compose exec php-fpm php artisan cache:clear

테스트

http://localhost/test

 

728x90
반응형