반응형
라라벨 개발 환경을 도커 컨테이너로 구성하는 방법
테스트 환경
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
반응형
'리눅스' 카테고리의 다른 글
| 우분투에서 NFS Server를 설치하고 NFS 볼륨을 마운트하는 방법 (1) | 2025.11.24 |
|---|---|
| 라라벨에서 현재 사용 중인 Redis 모듈이 PhpRedis인지 Predis인지 확인하는 방법 (1) | 2025.11.24 |
| 우분투 24.04에서 Keepalived와 Predixy를 사용해 Redis 클러스터를 구성하는 방법 (1) | 2025.11.18 |
| 우분투에서 기본 쉘을 dash에서 bash로 변경하는 방법 (0) | 2025.11.17 |
| HAProxy Statistics Page(Statistics Dashboard) (0) | 2025.11.14 |