feat: implement zero-downtime Green/Blue deployment using local TCP proxy
TaxBaik CI/CD / build-and-deploy (push) Successful in 51s
TaxBaik CI/CD / build-and-deploy (push) Successful in 51s
This commit is contained in:
@@ -100,6 +100,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Package artifact
|
- name: Package artifact
|
||||||
run: |
|
run: |
|
||||||
|
cp deploy_gb.sh ./publish/deploy_gb.sh
|
||||||
tar -czf taxbaik_deploy.tgz -C ./publish .
|
tar -czf taxbaik_deploy.tgz -C ./publish .
|
||||||
echo "✓ Package: $(du -sh taxbaik_deploy.tgz | cut -f1)"
|
echo "✓ Package: $(du -sh taxbaik_deploy.tgz | cut -f1)"
|
||||||
|
|
||||||
@@ -163,11 +164,9 @@ jobs:
|
|||||||
test -s "\$DEPLOY_DIR/appsettings.Production.json" \
|
test -s "\$DEPLOY_DIR/appsettings.Production.json" \
|
||||||
|| { echo "FATAL: appsettings.Production.json 없음" >&2; exit 1; }
|
|| { echo "FATAL: appsettings.Production.json 없음" >&2; exit 1; }
|
||||||
|
|
||||||
echo "--- [3/5] 심볼릭 링크 전환 ---"
|
echo "--- [3/4] Green-Blue 배포 실행 ---"
|
||||||
ln -sfn "\$DEPLOY_DIR" "\$DEPLOY_HOME/taxbaik_active"
|
chmod +x "\$DEPLOY_DIR/deploy_gb.sh"
|
||||||
|
"\$DEPLOY_DIR/deploy_gb.sh" "\$DEPLOY_DIR"
|
||||||
echo "--- [4/5] 서비스 재시작 ---"
|
|
||||||
sudo /usr/bin/systemctl restart taxbaik
|
|
||||||
|
|
||||||
echo "--- [5/5] 헬스 체크 (최대 60초) ---"
|
echo "--- [5/5] 헬스 체크 (최대 60초) ---"
|
||||||
ATTEMPTS=20
|
ATTEMPTS=20
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
private const string PortFile = "/home/kjh2064/taxbaik_port";
|
||||||
|
private static int _fallbackPort = 5003;
|
||||||
|
|
||||||
|
static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
// Allow setting fallback port via args
|
||||||
|
if (args.Length > 0 && int.TryParse(args[0], out var port))
|
||||||
|
{
|
||||||
|
_fallbackPort = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
var listener = new TcpListener(IPAddress.Loopback, 5001);
|
||||||
|
listener.Start();
|
||||||
|
Console.WriteLine($"[TaxBaik Proxy] Listening on 127.0.0.1:5001 (Forwarding to target in {PortFile})");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = await listener.AcceptTcpClientAsync();
|
||||||
|
_ = HandleClientAsync(client);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TaxBaik Proxy] Accept error: {ex.Message}");
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetTargetPort()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(PortFile))
|
||||||
|
{
|
||||||
|
var content = File.ReadAllText(PortFile).Trim();
|
||||||
|
if (int.TryParse(content, out var port) && port > 1024 && port < 65535)
|
||||||
|
{
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return _fallbackPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleClientAsync(TcpClient client)
|
||||||
|
{
|
||||||
|
client.NoDelay = true;
|
||||||
|
int targetPort = GetTargetPort();
|
||||||
|
using var backend = new TcpClient();
|
||||||
|
backend.NoDelay = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||||
|
await backend.ConnectAsync(IPAddress.Loopback, targetPort, cts.Token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TaxBaik Proxy] Failed to connect to backend on port {targetPort}: {ex.Message}");
|
||||||
|
client.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var clientStream = client.GetStream();
|
||||||
|
using var backendStream = backend.GetStream();
|
||||||
|
|
||||||
|
var toBackend = clientStream.CopyToAsync(backendStream);
|
||||||
|
var toClient = backendStream.CopyToAsync(clientStream);
|
||||||
|
|
||||||
|
await Task.WhenAny(toBackend, toClient);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
client.Close();
|
||||||
|
backend.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEPLOY_HOME="/home/kjh2064"
|
||||||
|
PORT_FILE="$DEPLOY_HOME/taxbaik_port"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
echo "===== 🚀 TaxBaik Green/Blue Deployment Script ====="
|
||||||
|
|
||||||
|
# 1. Determine active port
|
||||||
|
ACTIVE_PORT=5003
|
||||||
|
if [ -f "$PORT_FILE" ]; then
|
||||||
|
ACTIVE_PORT=$(cat "$PORT_FILE" | tr -d '[:space:]')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Determine target port
|
||||||
|
TARGET_PORT=5003
|
||||||
|
if [ "$ACTIVE_PORT" -eq 5003 ]; then
|
||||||
|
TARGET_PORT=5004
|
||||||
|
else
|
||||||
|
TARGET_PORT=5003
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Active Port: $ACTIVE_PORT"
|
||||||
|
echo "Target Port: $TARGET_PORT"
|
||||||
|
|
||||||
|
# 3. New deploy dir is passed as first argument
|
||||||
|
DEPLOY_DIR="$1"
|
||||||
|
if [ -z "$DEPLOY_DIR" ]; then
|
||||||
|
echo "Error: Deployment directory argument required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Deploy Directory: $DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 4. Start the new app on the target port
|
||||||
|
echo "=== Starting New App on Port $TARGET_PORT ==="
|
||||||
|
cd "$DEPLOY_DIR"
|
||||||
|
export ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
export ASPNETCORE_URLS="http://127.0.0.1:$TARGET_PORT"
|
||||||
|
|
||||||
|
# Run dotnet process
|
||||||
|
nohup /usr/bin/dotnet TaxBaik.Web.dll > "web_${TARGET_PORT}.log" 2>&1 &
|
||||||
|
NEW_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Verify process is running
|
||||||
|
if ! ps -p $NEW_PID > /dev/null; then
|
||||||
|
echo "❌ Failed to start dotnet process on port $TARGET_PORT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Health Check Loop
|
||||||
|
echo "=== Health Checking Port $TARGET_PORT ==="
|
||||||
|
ATTEMPTS=20
|
||||||
|
SUCCESS=false
|
||||||
|
for i in $(seq 1 $ATTEMPTS); do
|
||||||
|
STATUS=$(curl -sf -o /dev/null -w '%{http_code}' "http://127.0.0.1:${TARGET_PORT}/taxbaik/healthz" 2>/dev/null || echo "000")
|
||||||
|
if [ "$STATUS" = "200" ]; then
|
||||||
|
echo "✓ Health check passed on port $TARGET_PORT (Attempt $i/$ATTEMPTS)"
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo " Waiting for health check... ($i/$ATTEMPTS, Status: $STATUS)"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$SUCCESS" = "false" ]; then
|
||||||
|
echo "❌ Health check failed. Rolling back..."
|
||||||
|
kill -9 $NEW_PID || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Switch Traffic
|
||||||
|
echo "=== Switching Traffic to Port $TARGET_PORT ==="
|
||||||
|
echo "$TARGET_PORT" > "$PORT_FILE"
|
||||||
|
echo "✓ Traffic routed to $TARGET_PORT"
|
||||||
|
|
||||||
|
# 7. Terminate Old App
|
||||||
|
echo "=== Stopping Old App on Port $ACTIVE_PORT ==="
|
||||||
|
# Find PID listening on ACTIVE_PORT
|
||||||
|
OLD_PID=$(ss -tlnp | grep ":$ACTIVE_PORT " | grep -oP 'pid=\K\d+' | head -n1)
|
||||||
|
if [ -n "$OLD_PID" ]; then
|
||||||
|
echo "Killing old process PID: $OLD_PID"
|
||||||
|
kill -15 $OLD_PID || kill -9 $OLD_PID
|
||||||
|
echo "✓ Old process terminated"
|
||||||
|
else
|
||||||
|
echo "No old process found on port $ACTIVE_PORT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 8. Cleanup old deployment directories (Keep last 5)
|
||||||
|
echo "=== Cleaning Up Old Deployments ==="
|
||||||
|
ls -1dt $DEPLOY_HOME/deployments/taxbaik_* 2>/dev/null | tail -n +6 | xargs rm -rf 2>/dev/null || true
|
||||||
|
echo "✓ Cleanup completed"
|
||||||
|
|
||||||
|
echo "===== ✅ Green/Blue Deployment Completed Successfully ====="
|
||||||
Reference in New Issue
Block a user