CI/CD Maintainability
CI/CD Maintainability
This document outlines the continuous integration and deployment strategies designed to ensure high maintainability, automated quality gates, and reliable delivery pipelines.
Pipeline Architecture
graph LR
A[Code Push] --> B[Build]
B --> C[Unit Tests]
C --> D[Integration Tests]
D --> E[Code Quality]
E --> F[Security Scan]
F --> G[Package]
G --> H[Deploy Dev]
H --> I[Deploy Staging]
I --> J[Deploy Production]
GitHub Actions Workflow
Main CI/CD Pipeline
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop, solid-refactoring ]
pull_request:
branches: [ main ]
env:
DOTNET_VERSION: '8.0'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: |
dotnet test --configuration Release --no-build \
--verbosity normal \
--collect:"XPlat Code Coverage" \
--results-directory ./coverage
- name: Code Coverage
uses: codecov/codecov-action@v3
with:
directory: ./coverage
fail_ci_if_error: true
verbose: true
security:
name: Security Scan
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
quality:
name: Code Quality
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
build-and-push:
name: Build and Push Docker Image
runs-on: ubuntu-latest
needs: [test, security, quality]
if: github.ref == 'refs/heads/main'
outputs:
image: ${{ steps.image.outputs.image }}
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@v4
- 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=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
id: build
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
deploy-dev:
name: Deploy to Development
runs-on: ubuntu-latest
needs: build-and-push
environment: development
steps:
- name: Deploy to Dev Environment
run: |
echo "Deploying to development environment"
# kubectl set image deployment/mcp-server mcp-server=${{ needs.build-and-push.outputs.image }}
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [build-and-push, deploy-dev]
environment: staging
steps:
- name: Deploy to Staging Environment
run: |
echo "Deploying to staging environment"
# kubectl set image deployment/mcp-server mcp-server=${{ needs.build-and-push.outputs.image }}
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [build-and-push, deploy-staging]
environment: production
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to Production Environment
run: |
echo "Deploying to production environment"
# kubectl set image deployment/mcp-server mcp-server=${{ needs.build-and-push.outputs.image }}
Quality Gates
Code Coverage Requirements
# .github/workflows/quality-gates.yml
name: Quality Gates
on:
pull_request:
branches: [ main ]
jobs:
coverage-check:
name: Coverage Requirements
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Run tests with coverage
run: |
dotnet test --collect:"XPlat Code Coverage" \
--results-directory ./coverage \
/p:CoverletOutputFormat=cobertura \
/p:Threshold=80 \
/p:ThresholdType=line \
/p:ThresholdStat=minimum
- name: Fail if coverage below 80%
run: |
coverage=$(grep -oP 'line-rate="\K[^"]*' ./coverage/coverage.cobertura.xml | awk '{print $1*100}')
if (( $(echo "$coverage < 80" | bc -l) )); then
echo "Coverage $coverage% is below required 80%"
exit 1
fi
Static Analysis
name: Static Analysis
on: [push, pull_request]
jobs:
analyze:
name: Analyze Code
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Build
run: dotnet build --configuration Release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
Deployment Strategies
Blue-Green Deployment
#!/bin/bash
# scripts/deploy-blue-green.sh
set -e
ENVIRONMENT=${1:-staging}
VERSION=${2:-latest}
NAMESPACE="mcp-server-${ENVIRONMENT}"
echo "Starting blue-green deployment for ${ENVIRONMENT} environment"
# Check current active deployment
CURRENT_ACTIVE=$(kubectl get service mcp-server-active -n $NAMESPACE -o jsonpath='{.spec.selector.version}' 2>/dev/null || echo "blue")
NEW_ACTIVE=$([ "$CURRENT_ACTIVE" = "blue" ] && echo "green" || echo "blue")
echo "Current active: $CURRENT_ACTIVE, Deploying to: $NEW_ACTIVE"
# Deploy to inactive environment
kubectl set image deployment/mcp-server-$NEW_ACTIVE \
mcp-server=ghcr.io/yourorg/mcp-server:$VERSION \
-n $NAMESPACE
# Wait for rollout to complete
kubectl rollout status deployment/mcp-server-$NEW_ACTIVE -n $NAMESPACE
# Health check
echo "Performing health checks..."
kubectl port-forward service/mcp-server-$NEW_ACTIVE 8080:8080 -n $NAMESPACE &
PORT_FORWARD_PID=$!
sleep 5
if curl -f http://localhost:8080/health; then
echo "Health check passed"
# Switch traffic
kubectl patch service mcp-server-active \
-p '{"spec":{"selector":{"version":"'$NEW_ACTIVE'"}}}' \
-n $NAMESPACE
echo "Traffic switched to $NEW_ACTIVE"
else
echo "Health check failed, rolling back"
exit 1
fi
# Cleanup
kill $PORT_FORWARD_PID 2>/dev/null || true
Rolling Updates
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: mcp-server
template:
metadata:
labels:
app: mcp-server
spec:
containers:
- name: mcp-server
image: ghcr.io/yourorg/mcp-server:latest
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
Automated Testing Integration
Test Execution Strategy
# .github/workflows/test-strategy.yml
name: Comprehensive Testing
on: [push, pull_request]
jobs:
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Run Unit Tests
run: |
dotnet test SampleMcpServer.Tests \
--filter "Category!=Integration" \
--logger trx \
--collect:"XPlat Code Coverage"
- name: Publish Test Results
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Unit Test Results
path: '**/*.trx'
reporter: dotnet-trx
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
needs: unit-tests
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Run Integration Tests
run: |
dotnet test SampleMcpServer.Tests \
--filter "Category=Integration" \
--logger trx
- name: Publish Integration Test Results
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Integration Test Results
path: '**/*.trx'
reporter: dotnet-trx
smoke-tests:
name: Smoke Tests
runs-on: ubuntu-latest
needs: [unit-tests, integration-tests]
services:
mcp-server:
image: mcr.microsoft.com/dotnet/sdk:8.0
ports:
- 8080:8080
options: >-
--health-cmd "curl -f http://localhost:8080/health || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Run Smoke Tests
run: |
# Test basic functionality
curl -f http://localhost:8080/health
# Add more smoke tests here
Environment Management
Environment Configuration
# .github/environments/development.yml
name: development
url: https://dev.mcp-server.com
protection_rules:
- type: required_reviewers
required_reviewers:
users: ["dev-team"]
- type: wait_timer
wait_timer: 0
# .github/environments/staging.yml
name: staging
url: https://staging.mcp-server.com
protection_rules:
- type: required_reviewers
required_reviewers:
users: ["qa-team", "dev-lead"]
- type: wait_timer
wait_timer: 5
# .github/environments/production.yml
name: production
url: https://mcp-server.com
protection_rules:
- type: required_reviewers
required_reviewers:
users: ["devops-team", "security-team"]
required_reviewer_count: 2
- type: wait_timer
wait_timer: 30
Infrastructure as Code
# terraform/environments/production/main.tf
terraform {
required_version = ">= 1.0"
backend "s3" {
bucket = "mcp-server-terraform-state"
key = "production/terraform.tfstate"
region = "us-west-2"
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
module "mcp_server" {
source = "../../modules/mcp-server"
environment = "production"
instance_count = 3
instance_type = "t3.medium"
# Security
enable_waf = true
enable_ssl = true
certificate_arn = var.ssl_certificate_arn
# Monitoring
enable_cloudwatch = true
log_retention_days = 30
# Networking
vpc_id = var.vpc_id
subnet_ids = var.private_subnet_ids
tags = {
Environment = "production"
Project = "mcp-server"
Team = "platform"
}
}
Monitoring and Alerting
Application Metrics
# k8s/monitoring.yaml
apiVersion: v1
kind: ServiceMonitor
metadata:
name: mcp-server-metrics
spec:
selector:
matchLabels:
app: mcp-server
endpoints:
- port: metrics
interval: 30s
path: /metrics
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: mcp-server-alerts
spec:
groups:
- name: mcp-server
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
for: 5m
annotations:
summary: "High error rate detected"
- alert: HighMemoryUsage
expr: container_memory_usage_bytes / container_spec_memory_limit_bytes > 0.8
for: 5m
annotations:
summary: "High memory usage detected"
Deployment Notifications
# .github/workflows/notifications.yml
name: Deployment Notifications
on:
workflow_run:
workflows: ["CI/CD Pipeline"]
types: [completed]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Notify Slack on Success
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: 8398a7/action-slack@v3
with:
status: success
text: "✅ Deployment successful to production"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
- name: Notify Slack on Failure
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
uses: 8398a7/action-slack@v3
with:
status: failure
text: "❌ Deployment failed - immediate attention required"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Maintenance Automation
Dependency Updates
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
reviewers:
- "dev-team"
commit-message:
prefix: "chore"
include: "scope"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
Automated Security Updates
# .github/workflows/security-updates.yml
name: Security Updates
on:
schedule:
- cron: '0 6 * * MON' # Weekly on Monday at 6 AM
jobs:
security-audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0'
- name: Run security audit
run: |
dotnet list package --vulnerable --include-transitive
- name: Create security issue
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Security vulnerabilities detected',
body: 'Automated security scan found vulnerabilities. Please review and update dependencies.',
labels: ['security', 'priority-high']
})
Next Steps
- Review Production-Ready Settings for deployment configuration
- Explore System Architecture for design details
- Check Testing Integration for quality assurance processes