Skip to content

Commit 0ab71d4

Browse files
jicowanclaude
andcommitted
Initial commit: ECS xDS Controller for Envoy
Implements a custom xDS control plane that discovers ECS services/tasks and dynamically configures Envoy for hostname-based routing to multi-tenant applications. Replaces Traefik with Envoy for 3000+ tenant routing. Key components: - xDS server using go-control-plane (LDS, RDS, CDS, EDS) - ECS discovery via AWS SDK (ListServices, DescribeTasks) - Hostname resolution from ECS service tags - Terraform infrastructure for VPC, ECS, ALB - CI/CD pipeline for automated deployment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0 parents  commit 0ab71d4

33 files changed

Lines changed: 3678 additions & 0 deletions

.github/workflows/ci-cd.yml

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
inputs:
10+
deploy:
11+
description: 'Deploy to ECS'
12+
required: true
13+
default: 'false'
14+
type: choice
15+
options:
16+
- 'true'
17+
- 'false'
18+
destroy:
19+
description: 'Destroy infrastructure'
20+
required: false
21+
default: 'false'
22+
type: choice
23+
options:
24+
- 'true'
25+
- 'false'
26+
27+
env:
28+
AWS_REGION: us-west-2
29+
TF_VERSION: 1.6.0
30+
GO_VERSION: '1.24'
31+
32+
jobs:
33+
# ==========================================================================
34+
# Build and Test
35+
# ==========================================================================
36+
build:
37+
name: Build and Test
38+
runs-on: ubuntu-latest
39+
40+
steps:
41+
- name: Checkout code
42+
uses: actions/checkout@v4
43+
44+
- name: Set up Go
45+
uses: actions/setup-go@v5
46+
with:
47+
go-version: ${{ env.GO_VERSION }}
48+
49+
- name: Cache Go modules
50+
uses: actions/cache@v4
51+
with:
52+
path: ~/go/pkg/mod
53+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
54+
restore-keys: |
55+
${{ runner.os }}-go-
56+
57+
- name: Download dependencies
58+
run: |
59+
cd ${{ github.workspace }}
60+
GOPROXY=direct go mod download
61+
62+
- name: Build xDS Controller
63+
run: |
64+
GOPROXY=direct go build -v ./...
65+
66+
- name: Run tests
67+
run: |
68+
GOPROXY=direct go test -v -race ./...
69+
70+
- name: Build mock service
71+
run: |
72+
cd mock-service
73+
GOPROXY=direct go build -v .
74+
75+
# ==========================================================================
76+
# Build and Push Docker Images
77+
# ==========================================================================
78+
docker:
79+
name: Build and Push Docker Images
80+
runs-on: ubuntu-latest
81+
needs: build
82+
if: github.event_name == 'push' || github.event.inputs.deploy == 'true'
83+
84+
outputs:
85+
xds_image: ${{ steps.build-xds.outputs.image }}
86+
mock_image: ${{ steps.build-mock.outputs.image }}
87+
88+
steps:
89+
- name: Checkout code
90+
uses: actions/checkout@v4
91+
92+
- name: Configure AWS credentials
93+
uses: aws-actions/configure-aws-credentials@v4
94+
with:
95+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
96+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
97+
aws-region: ${{ env.AWS_REGION }}
98+
99+
- name: Login to Amazon ECR
100+
id: login-ecr
101+
uses: aws-actions/amazon-ecr-login@v2
102+
103+
- name: Set up Docker Buildx
104+
uses: docker/setup-buildx-action@v3
105+
106+
# Initialize Terraform to get ECR URLs
107+
- name: Setup Terraform
108+
uses: hashicorp/setup-terraform@v3
109+
with:
110+
terraform_version: ${{ env.TF_VERSION }}
111+
terraform_wrapper: false
112+
113+
- name: Terraform Init
114+
working-directory: infrastructure
115+
run: terraform init
116+
117+
- name: Get ECR URLs
118+
id: ecr-urls
119+
working-directory: infrastructure
120+
run: |
121+
# Try to get existing outputs, or use defaults
122+
XDS_ECR=$(terraform output -raw ecr_xds_controller_url 2>/dev/null || echo "")
123+
MOCK_ECR=$(terraform output -raw ecr_mock_service_url 2>/dev/null || echo "")
124+
125+
if [ -z "$XDS_ECR" ]; then
126+
# ECR doesn't exist yet, create repos
127+
echo "Creating ECR repositories..."
128+
terraform apply -target=aws_ecr_repository.xds_controller -target=aws_ecr_repository.mock_service -auto-approve
129+
XDS_ECR=$(terraform output -raw ecr_xds_controller_url)
130+
MOCK_ECR=$(terraform output -raw ecr_mock_service_url)
131+
fi
132+
133+
echo "xds_ecr=$XDS_ECR" >> $GITHUB_OUTPUT
134+
echo "mock_ecr=$MOCK_ECR" >> $GITHUB_OUTPUT
135+
136+
- name: Build and push xDS Controller image
137+
id: build-xds
138+
uses: docker/build-push-action@v5
139+
with:
140+
context: .
141+
file: deployments/docker/Dockerfile
142+
push: true
143+
tags: |
144+
${{ steps.ecr-urls.outputs.xds_ecr }}:latest
145+
${{ steps.ecr-urls.outputs.xds_ecr }}:${{ github.sha }}
146+
cache-from: type=gha
147+
cache-to: type=gha,mode=max
148+
149+
- name: Build and push mock service image
150+
id: build-mock
151+
uses: docker/build-push-action@v5
152+
with:
153+
context: mock-service
154+
file: mock-service/Dockerfile
155+
push: true
156+
tags: |
157+
${{ steps.ecr-urls.outputs.mock_ecr }}:latest
158+
${{ steps.ecr-urls.outputs.mock_ecr }}:${{ github.sha }}
159+
cache-from: type=gha
160+
cache-to: type=gha,mode=max
161+
162+
- name: Output image tags
163+
run: |
164+
echo "xDS Controller image: ${{ steps.ecr-urls.outputs.xds_ecr }}:${{ github.sha }}"
165+
echo "Mock service image: ${{ steps.ecr-urls.outputs.mock_ecr }}:${{ github.sha }}"
166+
167+
# ==========================================================================
168+
# Deploy Infrastructure
169+
# ==========================================================================
170+
deploy:
171+
name: Deploy Infrastructure
172+
runs-on: ubuntu-latest
173+
needs: docker
174+
if: github.event_name == 'push' || github.event.inputs.deploy == 'true'
175+
environment: production
176+
177+
steps:
178+
- name: Checkout code
179+
uses: actions/checkout@v4
180+
181+
- name: Configure AWS credentials
182+
uses: aws-actions/configure-aws-credentials@v4
183+
with:
184+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
185+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
186+
aws-region: ${{ env.AWS_REGION }}
187+
188+
- name: Setup Terraform
189+
uses: hashicorp/setup-terraform@v3
190+
with:
191+
terraform_version: ${{ env.TF_VERSION }}
192+
193+
- name: Terraform Init
194+
working-directory: infrastructure
195+
run: terraform init
196+
197+
- name: Terraform Plan
198+
working-directory: infrastructure
199+
run: terraform plan -out=tfplan
200+
201+
- name: Terraform Apply
202+
working-directory: infrastructure
203+
run: terraform apply -auto-approve tfplan
204+
205+
- name: Get outputs
206+
id: tf-outputs
207+
working-directory: infrastructure
208+
run: |
209+
echo "alb_url=$(terraform output -raw alb_url)" >> $GITHUB_OUTPUT
210+
echo "cluster_name=$(terraform output -raw ecs_cluster_name)" >> $GITHUB_OUTPUT
211+
212+
- name: Wait for services to stabilize
213+
run: |
214+
echo "Waiting for ECS services to stabilize..."
215+
sleep 120
216+
217+
- name: Run smoke tests
218+
run: |
219+
ALB_URL="${{ steps.tf-outputs.outputs.alb_url }}"
220+
echo "Testing ALB at: $ALB_URL"
221+
222+
# Test each mock tenant
223+
for i in 1 2 3; do
224+
HOSTNAME="tenant-${i}.test.local"
225+
echo "Testing $HOSTNAME..."
226+
RESPONSE=$(curl -sf -H "Host: $HOSTNAME" "$ALB_URL/" || echo "FAILED")
227+
echo "Response: $RESPONSE"
228+
229+
if echo "$RESPONSE" | grep -q "tenant-${i}"; then
230+
echo "✓ $HOSTNAME routing works!"
231+
else
232+
echo "✗ $HOSTNAME routing failed!"
233+
fi
234+
done
235+
236+
- name: Print test commands
237+
working-directory: infrastructure
238+
run: |
239+
echo "=== Deployment Complete ==="
240+
echo ""
241+
terraform output test_commands
242+
echo ""
243+
echo "ALB URL: ${{ steps.tf-outputs.outputs.alb_url }}"
244+
245+
# ==========================================================================
246+
# Destroy Infrastructure (manual trigger only)
247+
# ==========================================================================
248+
destroy:
249+
name: Destroy Infrastructure
250+
runs-on: ubuntu-latest
251+
if: github.event.inputs.destroy == 'true'
252+
environment: production
253+
254+
steps:
255+
- name: Checkout code
256+
uses: actions/checkout@v4
257+
258+
- name: Configure AWS credentials
259+
uses: aws-actions/configure-aws-credentials@v4
260+
with:
261+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
262+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
263+
aws-region: ${{ env.AWS_REGION }}
264+
265+
- name: Setup Terraform
266+
uses: hashicorp/setup-terraform@v3
267+
with:
268+
terraform_version: ${{ env.TF_VERSION }}
269+
270+
- name: Terraform Init
271+
working-directory: infrastructure
272+
run: terraform init
273+
274+
- name: Terraform Destroy
275+
working-directory: infrastructure
276+
run: terraform destroy -auto-approve

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Binaries
2+
bin/
3+
*.exe
4+
*.exe~
5+
*.dll
6+
*.so
7+
*.dylib
8+
9+
# Test binary
10+
*.test
11+
12+
# Output of go coverage
13+
*.out
14+
15+
# IDE
16+
.idea/
17+
.vscode/
18+
*.swp
19+
*.swo
20+
21+
# OS
22+
.DS_Store
23+
24+
# Terraform
25+
*.tfstate
26+
*.tfstate.*
27+
.terraform/
28+
.terraform.lock.hcl
29+
30+
# Environment
31+
.env
32+
.env.local

0 commit comments

Comments
 (0)