devops-cicd
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDevOps & CI/CD
DevOps & CI/CD
Overview
概述
Practices for automating build, test, and deployment pipelines.
自动化构建、测试与部署流水线的实践方案。
CI/CD Pipeline
CI/CD流水线
Pipeline Stages
流水线阶段
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Commit │ → │ Build │ → │ Test │ → │ Deploy │ → │ Release │
│ │ │ │ │ │ │ Staging │ │ Prod │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ │ │
│ │ Compile │ │ Unit │ │ │
│ │ Lint │ │ Integ │ │ │
│ │ Type │ │ E2E │ │ │
│ └─────────┘ └─────────┘ │ │
│ │ │
Trigger Manual? Approval?┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Commit │ → │ Build │ → │ Test │ → │ Deploy │ → │ Release │
│ │ │ │ │ │ │ Staging │ │ Prod │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ │ │
│ │ Compile │ │ Unit │ │ │
│ │ Lint │ │ Integ │ │ │
│ │ Type │ │ E2E │ │ │
│ └─────────┘ └─────────┘ │ │
│ │ │
Trigger Manual? Approval?GitHub Actions
GitHub Actions
yaml
undefinedyaml
undefined.github/workflows/ci.yml
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
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: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Test
run: npm run test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
- name: Build
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: dist/deploy-staging:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to staging
run: |
# Deploy script here
echo "Deploying to staging..."deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- name: Deploy to production
run: |
echo "Deploying to production..."undefinedname: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
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: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Test
run: npm run test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
- name: Build
run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: dist/deploy-staging:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy to staging
run: |
# Deploy script here
echo "Deploying to staging..."deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.com
steps:
- name: Deploy to production
run: |
echo "Deploying to production..."undefinedMatrix Builds
矩阵构建
yaml
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: windows-latest
node: 18
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm testyaml
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: windows-latest
node: 18
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm testDeployment Strategies
部署策略
Blue-Green Deployment
蓝绿部署
Before:
┌─────────────────────────────────────────┐
│ Load Balancer │
└─────────────────┬───────────────────────┘
│
┌───────┴───────┐
↓ ↓
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (idle) │
│ ACTIVE │ │ │
└───────────┘ └───────────┘
Deploy v1.1 to Green:
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (v1.1) │
│ ACTIVE │ │ testing │
└───────────┘ └───────────┘
Switch traffic:
┌───────────────┐
↓ ↓
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (v1.1) │
│ standby │ │ ACTIVE │
└───────────┘ └───────────┘Before:
┌─────────────────────────────────────────┐
│ Load Balancer │
└─────────────────┬───────────────────────┘
│
┌───────┴───────┐
↓ ↓
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (idle) │
│ ACTIVE │ │ │
└───────────┘ └───────────┘
Deploy v1.1 to Green:
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (v1.1) │
│ ACTIVE │ │ testing │
└───────────┘ └───────────┘
Switch traffic:
┌───────────────┐
↓ ↓
┌───────────┐ ┌───────────┐
│ Blue │ │ Green │
│ (v1.0) │ │ (v1.1) │
│ standby │ │ ACTIVE │
└───────────┘ └───────────┘Canary Deployment
金丝雀部署
yaml
undefinedyaml
undefinedKubernetes canary with Argo Rollouts
Kubernetes canary with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to new version
- pause: { duration: 5m }
- setWeight: 30
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
analysis:
templates:
- templateName: success-rate
startingStep: 2 # Start analysis at 30%
args:
- name: service-name
value: my-app
undefinedapiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to new version
- pause: { duration: 5m }
- setWeight: 30
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
analysis:
templates:
- templateName: success-rate
startingStep: 2 # Start analysis at 30%
args:
- name: service-name
value: my-app
undefinedRolling Update
滚动更新
yaml
undefinedyaml
undefinedKubernetes rolling update
Kubernetes rolling update
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Can exceed replicas by 1
maxUnavailable: 0 # All must be available during update
template:
spec:
containers:
- name: app
image: my-app:v1.1
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Can exceed replicas by 1
maxUnavailable: 0 # All must be available during update
template:
spec:
containers:
- name: app
image: my-app:v1.1
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---Infrastructure as Code
基础设施即代码
Terraform
Terraform
hcl
undefinedhcl
undefinedmain.tf
main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}
Variables
Variables
variable "environment" {
type = string
default = "production"
}
variable "instance_count" {
type = number
default = 2
}
variable "environment" {
type = string
default = "production"
}
variable "instance_count" {
type = number
default = 2
}
Resources
Resources
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main-vpc"
Environment = var.environment
}
}
resource "aws_instance" "web" {
count = var.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-${count.index}"
}
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "main-vpc"
Environment = var.environment
}
}
resource "aws_instance" "web" {
count = var.instance_count
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-${count.index}"
}
}
Outputs
Outputs
output "instance_ips" {
value = aws_instance.web[*].public_ip
}
undefinedoutput "instance_ips" {
value = aws_instance.web[*].public_ip
}
undefinedTerraform Modules
Terraform 模块
hcl
undefinedhcl
undefinedmodules/vpc/main.tf
modules/vpc/main.tf
variable "cidr_block" {
type = string
}
variable "environment" {
type = string
}
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
tags = {
Environment = var.environment
}
}
output "vpc_id" {
value = aws_vpc.this.id
}
variable "cidr_block" {
type = string
}
variable "environment" {
type = string
}
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
tags = {
Environment = var.environment
}
}
output "vpc_id" {
value = aws_vpc.this.id
}
Using the module
Using the module
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
environment = "production"
}
resource "aws_subnet" "main" {
vpc_id = module.vpc.vpc_id
...
}
---module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
environment = "production"
}
resource "aws_subnet" "main" {
vpc_id = module.vpc.vpc_id
...
}
---Docker
Docker
Dockerfile Best Practices
Dockerfile 最佳实践
dockerfile
undefineddockerfile
undefinedUse specific version tags
Use specific version tags
FROM node:20-alpine AS builder
FROM node:20-alpine AS builder
Set working directory
Set working directory
WORKDIR /app
WORKDIR /app
Copy dependency files first (layer caching)
Copy dependency files first (layer caching)
COPY package*.json ./
COPY package*.json ./
Install dependencies
Install dependencies
RUN npm ci --only=production
RUN npm ci --only=production
Copy source code
Copy source code
COPY . .
COPY . .
Build application
Build application
RUN npm run build
RUN npm run build
Production stage - smaller final image
Production stage - smaller final image
FROM node:20-alpine AS production
WORKDIR /app
FROM node:20-alpine AS production
WORKDIR /app
Create non-root user
Create non-root user
RUN addgroup -g 1001 -S nodejs &&
adduser -S nodejs -u 1001
adduser -S nodejs -u 1001
RUN addgroup -g 1001 -S nodejs &&
adduser -S nodejs -u 1001
adduser -S nodejs -u 1001
Copy from builder
Copy from builder
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
Use non-root user
Use non-root user
USER nodejs
USER nodejs
Expose port
Expose port
EXPOSE 3000
EXPOSE 3000
Health check
Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s
CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1
CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s
CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1
CMD wget --quiet --tries=1 --spider http://localhost:3000/health || exit 1
Run application
Run application
CMD ["node", "dist/index.js"]
undefinedCMD ["node", "dist/index.js"]
undefinedDocker Compose
Docker Compose
yaml
undefinedyaml
undefineddocker-compose.yml
docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
---version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
---Kubernetes
Kubernetes
Basic Deployment
基础部署
yaml
undefinedyaml
undefineddeployment.yaml
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: my-app labels: app: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-app image: my-app:v1.0.0 ports: - containerPort: 3000 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" livenessProbe: httpGet: path: /health/live port: 3000 initialDelaySeconds: 3 periodSeconds: 10 readinessProbe: httpGet: path: /health/ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: database-url
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
undefinedapiVersion: apps/v1 kind: Deployment metadata: name: my-app labels: app: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-app image: my-app:v1.0.0 ports: - containerPort: 3000 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" livenessProbe: httpGet: path: /health/live port: 3000 initialDelaySeconds: 3 periodSeconds: 10 readinessProbe: httpGet: path: /health/ready port: 3000 initialDelaySeconds: 5 periodSeconds: 5 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: database-url
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
undefinedConfigMaps and Secrets
ConfigMap 与 Secret
yaml
undefinedyaml
undefinedconfigmap.yaml
configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
secret.yaml (base64 encoded)
secret.yaml (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
database-url: cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2Ri
---apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
database-url: cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2Ri
---GitOps
GitOps
ArgoCD Application
ArgoCD 应用
yaml
undefinedyaml
undefinedapplication.yaml
application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/k8s-manifests.git
targetRevision: HEAD
path: apps/my-app/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
---apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/k8s-manifests.git
targetRevision: HEAD
path: apps/my-app/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
---Feature Flags
功能开关
typescript
// Using LaunchDarkly / Unleash pattern
interface FeatureFlags {
newCheckoutFlow: boolean;
betaFeatures: boolean;
maxUploadSize: number;
}
class FeatureFlagService {
constructor(private client: FeatureFlagClient) {}
async isEnabled(flag: keyof FeatureFlags, user?: User): Promise<boolean> {
return this.client.getBooleanValue(flag, false, {
userId: user?.id,
email: user?.email,
groups: user?.groups
});
}
}
// Usage
if (await featureFlags.isEnabled('newCheckoutFlow', user)) {
return <NewCheckoutFlow />;
} else {
return <LegacyCheckout />;
}typescript
// Using LaunchDarkly / Unleash pattern
interface FeatureFlags {
newCheckoutFlow: boolean;
betaFeatures: boolean;
maxUploadSize: number;
}
class FeatureFlagService {
constructor(private client: FeatureFlagClient) {}
async isEnabled(flag: keyof FeatureFlags, user?: User): Promise<boolean> {
return this.client.getBooleanValue(flag, false, {
userId: user?.id,
email: user?.email,
groups: user?.groups
});
}
}
// Usage
if (await featureFlags.isEnabled('newCheckoutFlow', user)) {
return <NewCheckoutFlow />;
} else {
return <LegacyCheckout />;
}Related Skills
相关技能
- [[testing-strategies]] - CI test integration
- [[reliability-engineering]] - Deployment reliability
- [[monitoring-observability]] - Deployment monitoring
- [[testing-strategies]] - CI 测试集成
- [[reliability-engineering]] - 部署可靠性
- [[monitoring-observability]] - 部署监控