Przejdź do głównej zawartości

Automatyzacja wdrożeń

Wdrożenie to moment gdy kod spotyka się z rzeczywistością. Niezależnie od tego, czy pushujiesz na jeden serwer, czy orkiestrujesz w globalnych regionach, Claude Code przekształca wdrożenie z obgryzania paznokci w pewny, zautomatyzowany proces. Ta lekcja eksploruje jak wykorzystać asystę AI do nowoczesnych praktyk wdrażania.

Scenariusz: Twój zespół wdraża 20 razy dziennie w 5 środowiskach i 3 regionach. Każde wdrożenie obejmuje budowę Docker, manifesty Kubernetes, migracje bazy danych, feature flags i procedury rollback. Jeden błąd może położyć produkcję. Tradycyjne podejście: specjaliści od wdrożeń i runbooki. Z Claude Code: inteligentna automatyzacja.

Dzień 1: Ręczna konfiguracja Docker
- Pisanie Dockerfile ręcznie
- Debugowanie problemów budowy jeden po jednym
- Ręczne testowanie kontenera
Dzień 2-3: Konfiguracja Kubernetes
- Copy-paste manifestów YAML
- Ręczna aktualizacja wersji
- Liczenie na to że nic się nie zepsuje
Dzień 4-5: Pipeline CI/CD
- Pisanie skryptów wdrażania
- Konfiguracja środowisk
- Testowanie na staging (trzymanie kciuków)
Dzień 6+: Problemy produkcyjne
- Szamotanie się z naprawami
- Ręczne rollbacki
- Aktualizacja runbooków
> Stwórz gotowy do produkcji Dockerfile dla naszej aplikacji Node.js:
> - Budowę wieloetapową dla minimalnego rozmiaru
> - Wzmocnienie bezpieczeństwa
> - Optymalizację cache'owania czasu budowy
> - Wykonanie użytkownika nie-root
> - Kontrole zdrowia

Claude generuje zoptymalizowany Dockerfile:

# Etap budowy
FROM node:20-alpine AS builder
# Zainstaluj zależności budowy
RUN apk add --no-cache python3 make g++
# Stwórz katalog aplikacji
WORKDIR /build
# Skopiuj pliki zależności jako pierwsze (lepsze cache'owanie)
COPY package*.json ./
COPY yarn.lock* ./
COPY pnpm-lock.yaml* ./
# Zainstaluj zależności na podstawie lockfile
RUN if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
elif [ -f pnpm-lock.yaml ]; then corepack enable && pnpm install --frozen-lockfile; \
else npm ci; fi
# Skopiuj kod źródłowy
COPY . .
# Zbuduj aplikację
RUN npm run build
# Usuń dev dependencies
RUN npm prune --production
# Etap produkcyjny
FROM node:20-alpine AS runtime
# Zainstaluj tylko runtime dependencies
RUN apk add --no-cache tini curl
# Stwórz użytkownika nie-root
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Ustaw katalog roboczy
WORKDIR /app
# Skopiuj zbudowaną aplikację z buildera
COPY --from=builder --chown=nodejs:nodejs /build/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /build/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /build/package*.json ./
# Eksponuj port (tylko dokumentacja)
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Przełącz na użytkownika nie-root
USER nodejs
# Użyj tini do prawidłowej obsługi sygnałów
ENTRYPOINT ["/sbin/tini", "--"]
# Uruchom aplikację
CMD ["node", "dist/index.js"]
> Stwórz konfigurację Docker Compose do lokalnego developmentu z:
> - Kontenerem aplikacji z hot reload
> - Bazą danych PostgreSQL
> - Cache Redis
> - Reverse proxy Nginx
> - Odpowiednim networkingiem i woluminami
docker-compose.yml
version: '3.9'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
container_name: myapp_app
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://user:password@postgres:5432/myapp
REDIS_URL: redis://redis:6379
volumes:
- ./src:/app/src:delegated
- ./public:/app/public:delegated
- node_modules:/app/node_modules
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- myapp_network
command: npm run dev
postgres:
image: postgres:15-alpine
container_name: myapp_postgres
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
timeout: 5s
retries: 5
networks:
- myapp_network
redis:
image: redis:7-alpine
container_name: myapp_redis
command: redis-server --appendonly yes
volumes:
- redis_data:/data
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- myapp_network
nginx:
image: nginx:alpine
container_name: myapp_nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- myapp_network
volumes:
node_modules:
postgres_data:
redis_data:
networks:
myapp_network:
driver: bridge
> Wygeneruj manifesty Kubernetes dla naszej aplikacji:
> - Deployment z rolling updates
> - Service do równoważenia obciążenia
> - Ingress z TLS
> - ConfigMap i Secrets
> - HorizontalPodAutoscaler
> - PodDisruptionBudget

Deployment

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
labels:
app: myapp
version: v1.0.0
spec:
replicas: 3
selector:
matchLabels:
app: myapp
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: myapp
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: kubernetes.io/hostname
containers:
- name: myapp
image: myregistry/myapp:v1.0.0
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http
- containerPort: 9090
name: metrics
env:
- name: NODE_ENV
value: production
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-url
envFrom:
- configMapRef:
name: myapp-config
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
securityContext:
runAsNonRoot: true
runAsUser: 1001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumeMounts:
- name: temp
mountPath: /tmp
- name: cache
mountPath: /app/.cache
volumes:
- name: temp
emptyDir: {}
- name: cache
emptyDir: {}
imagePullSecrets:
- name: registry-credentials

Service & Ingress

service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: production
labels:
app: myapp
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 3000
protocol: TCP
name: http
selector:
app: myapp
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
namespace: production
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- api.example.com
secretName: myapp-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80

Autoscaling & PDB

hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60
---
# pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myapp
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app: myapp

Config & Secrets

configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: production
data:
LOG_LEVEL: info
CACHE_TTL: "3600"
MAX_CONNECTIONS: "100"
FEATURE_FLAGS: |
{
"newUI": true,
"betaFeatures": false
}
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
namespace: production
type: Opaque
stringData:
database-url: postgresql://user:password@postgres:5432/myapp
redis-url: redis://:password@redis:6379
api-key: your-api-key-here
jwt-secret: your-jwt-secret-here
> Stwórz wykres Helm dla naszej aplikacji z:
> - Sparametryzowanymi wartościami
> - Nadpisaniami specyficznymi dla środowisk
> - Zarządzaniem zależnościami
> - Jobami hook dla migracji
Chart.yaml
apiVersion: v2
name: myapp
description: A Helm chart for MyApp
type: application
version: 1.0.0
appVersion: "1.0.0"
dependencies:
- name: postgresql
version: "12.1.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "17.3.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
# values.yaml
replicaCount: 3
image:
repository: myregistry/myapp
pullPolicy: IfNotPresent
tag: "" # Nadpisuje tag obrazu
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- api.example.com
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
postgresql:
enabled: true
auth:
username: myapp
password: changeme
database: myapp
redis:
enabled: true
auth:
enabled: true
password: changeme
# Wartości specyficzne dla środowisk
environments:
production:
replicaCount: 5
resources:
limits:
cpu: 1000m
memory: 1Gi
staging:
replicaCount: 2
ingress:
hosts:
- host: api-staging.example.com
> Stwórz kompletny workflow GitHub Actions dla:
> - Budowy i testowania na PR
> - Skanowania bezpieczeństwa
> - Budowy i pushowania obrazów Docker
> - Wdrażania na staging przy merge
> - Wdrożenia produkcyjnego z zatwierdzeniem
.github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm run test:ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'CRITICAL,HIGH'
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
build:
needs: [test, security]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image: ${{ steps.image.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Output image
id: image
run: echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
deploy-staging:
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- name: Deploy to Kubernetes
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG_STAGING }}
run: |
echo "$KUBE_CONFIG" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
# Zaktualizuj obraz w deployment
kubectl set image deployment/myapp myapp=${{ needs.build.outputs.image }} -n staging
# Czekaj na rollout
kubectl rollout status deployment/myapp -n staging --timeout=5m
# Uruchom smoke tests
./scripts/smoke-test.sh staging
deploy-production:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Create release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ github.run_number }}
release_name: Release v${{ github.run_number }}
draft: false
prerelease: false
- name: Deploy to Production
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG_PRODUCTION }}
run: |
echo "$KUBE_CONFIG" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
# Blue-green deployment
./scripts/blue-green-deploy.sh ${{ needs.build.outputs.image }}
> Stwórz pipeline GitLab CI z podobnymi funkcjami
.gitlab-ci.yml
stages:
- test
- security
- build
- deploy
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
REGISTRY: registry.gitlab.com
IMAGE_NAME: $CI_PROJECT_PATH
# Etap testów
test:unit:
stage: test
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
script:
- npm ci
- npm run lint
- npm run test:ci
coverage: '/Coverage: \d+\.\d+%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
# Skanowanie bezpieczeństwa
security:scan:
stage: security
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy fs --severity HIGH,CRITICAL --exit-code 1 .
allow_failure: true
# Budowa obrazu Docker
build:image:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA .
- docker push $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA
- docker tag $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA $REGISTRY/$IMAGE_NAME:latest
- docker push $REGISTRY/$IMAGE_NAME:latest
only:
- main
- develop
# Wdrożenie na staging
deploy:staging:
stage: deploy
image: bitnami/kubectl:latest
environment:
name: staging
url: https://staging.example.com
script:
- echo "$KUBE_CONFIG_STAGING" | base64 -d > /tmp/kubeconfig
- export KUBECONFIG=/tmp/kubeconfig
- kubectl set image deployment/myapp myapp=$REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA -n staging
- kubectl rollout status deployment/myapp -n staging
only:
- develop
# Wdrożenie na produkcję
deploy:production:
stage: deploy
image: bitnami/kubectl:latest
environment:
name: production
url: https://api.example.com
script:
- echo "$KUBE_CONFIG_PRODUCTION" | base64 -d > /tmp/kubeconfig
- export KUBECONFIG=/tmp/kubeconfig
- kubectl set image deployment/myapp myapp=$REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA -n production
- kubectl rollout status deployment/myapp -n production
when: manual
only:
- main
> Zaimplementuj strategię wdrożenia blue-green:
> - Wdrożenie zero-downtime
> - Możliwość natychmiastowego rollback
> - Przełączanie ruchu
> - Walidacja zdrowia
scripts/blue-green-deploy.sh
#!/bin/bash
set -e
IMAGE=$1
NAMESPACE=${2:-production}
APP_NAME=${3:-myapp}
echo "Starting blue-green deployment..."
# Określ obecnie aktywne wdrożenie
CURRENT_ACTIVE=$(kubectl get service $APP_NAME -n $NAMESPACE -o jsonpath='{.spec.selector.version}')
if [ "$CURRENT_ACTIVE" = "blue" ]; then
NEW_VERSION="green"
OLD_VERSION="blue"
else
NEW_VERSION="blue"
OLD_VERSION="green"
fi
echo "Current active: $OLD_VERSION, deploying to: $NEW_VERSION"
# Zaktualizuj nieaktywne wdrożenie
kubectl set image deployment/$APP_NAME-$NEW_VERSION \
$APP_NAME=$IMAGE \
-n $NAMESPACE
# Czekaj aż nowe wdrożenie będzie gotowe
echo "Waiting for $NEW_VERSION deployment to be ready..."
kubectl rollout status deployment/$APP_NAME-$NEW_VERSION -n $NAMESPACE --timeout=10m
# Uruchom kontrole zdrowia
echo "Running health checks on $NEW_VERSION..."
NEW_PODS=$(kubectl get pods -n $NAMESPACE -l app=$APP_NAME,version=$NEW_VERSION -o jsonpath='{.items[*].metadata.name}')
for pod in $NEW_PODS; do
kubectl exec $pod -n $NAMESPACE -- curl -f http://localhost:3000/health || {
echo "Health check failed for pod $pod"
exit 1
}
done
# Przełącz ruch na nową wersję
echo "Switching traffic to $NEW_VERSION..."
kubectl patch service $APP_NAME -n $NAMESPACE -p \
'{"spec":{"selector":{"version":"'$NEW_VERSION'"}}}'
# Zweryfikuj przełączenie ruchu
sleep 5
ACTIVE_VERSION=$(kubectl get service $APP_NAME -n $NAMESPACE -o jsonpath='{.spec.selector.version}')
if [ "$ACTIVE_VERSION" != "$NEW_VERSION" ]; then
echo "Failed to switch traffic to $NEW_VERSION"
exit 1
fi
echo "Successfully deployed $NEW_VERSION"
echo "Old version $OLD_VERSION is still running for quick rollback"
# Opcjonalnie: Przeskaluj starą wersję po udanym wdrożeniu
# kubectl scale deployment/$APP_NAME-$OLD_VERSION --replicas=0 -n $NAMESPACE
> Skonfiguruj wdrożenie canary używając service mesh Istio:
> - Stopniowe przesuwanie ruchu
> - Możliwość testów A/B
> - Automatyczny rollback przy błędach
canary-virtualservice.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp-canary
namespace: production
spec:
hosts:
- myapp
http:
- match:
- headers:
canary:
exact: "true"
route:
- destination:
host: myapp
subset: canary
weight: 100
- route:
- destination:
host: myapp
subset: stable
weight: 90
- destination:
host: myapp
subset: canary
weight: 10
---
# destinationrule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: myapp-destination
namespace: production
spec:
host: myapp
subsets:
- name: stable
labels:
version: stable
- name: canary
labels:
version: canary
---
# Progressive rollout z Flagger
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: myapp
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
service:
port: 80
analysis:
interval: 1m
threshold: 5
maxWeight: 50
stepWeight: 10
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500
interval: 1m
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://myapp.production:80/"
> Stwórz definicję zadania AWS ECS i serwis:
> - Wdrożenie Fargate
> - Konfiguracja auto-scaling
> - Integracja load balancer
> - Logowanie CloudWatch
task-definition.json
{
"family": "myapp",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"containerDefinitions": [
{
"name": "myapp",
"image": "myregistry/myapp:latest",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{
"name": "NODE_ENV",
"value": "production"
}
],
"secrets": [
{
"name": "DATABASE_URL",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789:secret:myapp/database-url"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/myapp",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
]
}
# Skrypt wdrażania
#!/bin/bash
# Zarejestruj definicję zadania
aws ecs register-task-definition --cli-input-json file://task-definition.json
# Zaktualizuj serwis
aws ecs update-service \
--cluster production \
--service myapp \
--task-definition myapp:latest \
--desired-count 3 \
--deployment-configuration maximumPercent=200,minimumHealthyPercent=100
# Czekaj na wdrożenie
aws ecs wait services-stable --cluster production --services myapp
> Wdróż na Google Cloud Run z:
> - Automatycznym skalowaniem
> - Domeną niestandardową
> - Konfiguracją środowiska
service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: myapp
annotations:
run.googleapis.com/ingress: all
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "100"
run.googleapis.com/cpu-throttling: "false"
spec:
containers:
- image: gcr.io/myproject/myapp:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
resources:
limits:
cpu: "2"
memory: "2Gi"
livenessProbe:
httpGet:
path: /health
initialDelaySeconds: 10
periodSeconds: 10
startupProbe:
httpGet:
path: /ready
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30
Okno terminala
# Skrypt wdrażania
gcloud run deploy myapp \
--image gcr.io/myproject/myapp:latest \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars NODE_ENV=production \
--set-secrets DATABASE_URL=database-url:latest \
--min-instances 1 \
--max-instances 100 \
--memory 2Gi \
--cpu 2
> Stwórz strategię migracji która zapewnia zero downtime:
> - Migracje przed wdrożeniem
> - Zmiany wstecznie kompatybilne
> - Czyszczenie po wdrożeniu
scripts/migrate-safe.sh
#!/bin/bash
set -e
PHASE=$1
DATABASE_URL=$2
case $PHASE in
"pre-deploy")
echo "Running pre-deployment migrations..."
# Dodaj nowe kolumny (nullable lub z domyślnymi)
npx knex migrate:up --env production --specific 001_add_new_columns.js
# Dodaj nowe tabele
npx knex migrate:up --env production --specific 002_create_new_tables.js
# Stwórz nowe indeksy (concurrently)
psql $DATABASE_URL -c "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email_new ON users(email);"
;;
"post-deploy")
echo "Running post-deployment migrations..."
# Backfill danych
npx knex migrate:up --env production --specific 003_backfill_data.js
# Usuń stare kolumny
npx knex migrate:up --env production --specific 004_drop_old_columns.js
# Zmień nazwy indeksów
psql $DATABASE_URL -c "DROP INDEX IF EXISTS idx_users_email_old;"
;;
"rollback")
echo "Rolling back migrations..."
npx knex migrate:down --env production
;;
*)
echo "Usage: $0 {pre-deploy|post-deploy|rollback}"
exit 1
;;
esac
> Zaimplementuj kompleksowe kontrole zdrowia:
> - Liveness probe
> - Readiness probe
> - Kontrole zależności
> - Graceful shutdown
health.js
const express = require('express');
const { Pool } = require('pg');
const Redis = require('ioredis');
const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const redis = new Redis(process.env.REDIS_URL);
let isShuttingDown = false;
const dependencies = new Map();
// Liveness probe - czy aplikacja działa?
app.get('/health', (req, res) => {
if (isShuttingDown) {
return res.status(503).json({ status: 'shutting_down' });
}
res.json({ status: 'healthy', uptime: process.uptime() });
});
// Readiness probe - czy aplikacja może obsługiwać ruch?
app.get('/ready', async (req, res) => {
if (isShuttingDown) {
return res.status(503).json({ status: 'shutting_down' });
}
const checks = await Promise.allSettled([
checkDatabase(),
checkRedis(),
checkExternalAPI()
]);
const allHealthy = checks.every(check => check.status === 'fulfilled' && check.value.healthy);
if (allHealthy) {
res.json({
status: 'ready',
dependencies: Object.fromEntries(dependencies)
});
} else {
res.status(503).json({
status: 'not_ready',
dependencies: Object.fromEntries(dependencies)
});
}
});
async function checkDatabase() {
try {
const result = await pool.query('SELECT 1');
dependencies.set('postgres', { healthy: true });
return { healthy: true };
} catch (error) {
dependencies.set('postgres', { healthy: false, error: error.message });
return { healthy: false };
}
}
async function checkRedis() {
try {
await redis.ping();
dependencies.set('redis', { healthy: true });
return { healthy: true };
} catch (error) {
dependencies.set('redis', { healthy: false, error: error.message });
return { healthy: false };
}
}
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, starting graceful shutdown...');
isShuttingDown = true;
// Przestań przyjmować nowe requesty
server.close(() => {
console.log('HTTP server closed');
// Zamknij połączenia z bazą danych
pool.end(() => {
console.log('Database pool closed');
// Zamknij połączenie Redis
redis.disconnect();
console.log('Redis disconnected');
process.exit(0);
});
});
// Wymuszony shutdown po 30 sekundach
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 30000);
});
> Stwórz system automatycznego rollback:
> - Monitoruj error rates
> - Automatyczne triggery rollback
> - System notyfikacji
scripts/monitor-and-rollback.sh
#!/bin/bash
NAMESPACE=$1
APP_NAME=$2
ERROR_THRESHOLD=5
CHECK_INTERVAL=30
MAX_CHECKS=10
echo "Monitoring deployment for $APP_NAME in $NAMESPACE..."
for i in $(seq 1 $MAX_CHECKS); do
sleep $CHECK_INTERVAL
# Pobierz aktualny error rate z metryk
ERROR_RATE=$(kubectl exec -n $NAMESPACE deployment/$APP_NAME -- \
curl -s http://localhost:9090/metrics | \
grep 'http_requests_errors_total' | \
awk '{print $2}')
# Pobierz całkowitą liczbę requestów
TOTAL_REQUESTS=$(kubectl exec -n $NAMESPACE deployment/$APP_NAME -- \
curl -s http://localhost:9090/metrics | \
grep 'http_requests_total' | \
awk '{print $2}')
# Oblicz procent błędów
if [ "$TOTAL_REQUESTS" -gt 0 ]; then
ERROR_PERCENT=$(awk "BEGIN {printf \"%.2f\", ($ERROR_RATE/$TOTAL_REQUESTS)*100}")
echo "Check $i/$MAX_CHECKS - Error rate: $ERROR_PERCENT%"
if (( $(echo "$ERROR_PERCENT > $ERROR_THRESHOLD" | bc -l) )); then
echo "Error rate exceeded threshold! Initiating rollback..."
# Uruchom rollback
kubectl rollout undo deployment/$APP_NAME -n $NAMESPACE
# Wyślij notyfikację
curl -X POST $SLACK_WEBHOOK_URL \
-H 'Content-type: application/json' \
--data "{\"text\":\"🚨 Automatic rollback triggered for $APP_NAME due to high error rate: $ERROR_PERCENT%\"}"
exit 1
fi
fi
done
echo "Deployment monitoring complete - all checks passed!"

Nauczyłeś się jak wykorzystać Claude Code do kompleksowej automatyzacji wdrożeń - od konteneryzacji po orkiestrację po wdrożenia zero-downtime. Kluczem jest traktowanie wdrożenia jako kodu, który może być generowany, optymalizowany i utrzymywany z asystą AI.

Pamiętaj: Świetne wdrożenia są przewidywalne, powtarzalne i odwracalne. Użyj Claude Code aby zakodować najlepsze praktyki w swoim pipeline wdrażania, zapewniając że każde wydanie jest tak płynne jak to możliwe, jednocześnie zachowując możliwość szybkiego odzyskania po problemach.