Skip to content

Commit 7ccd33b

Browse files
committed
Dual write example
1 parent 1612607 commit 7ccd33b

File tree

7 files changed

+602
-2
lines changed

7 files changed

+602
-2
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
scala/datastax-v4/aws-glue/dist/
2-
scala/datastax-v4/aws-glue/.DS_Store
3-
scala/datastax-v4/.DS_Store
2+
**/*.DS_Store

migration/online/zdm-proxy/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM datastax/zdm-proxy:latest
2+
3+
# Install tools
4+
RUN apk add --no-cache socat bind-tools curl
5+
6+
RUN mkdir -p /app
7+
8+
ADD https://certs.secureserver.net/repository/sf-class2-root.crt /app/sf-class2-root.crt
9+
10+
# Declare build-time ARG
11+
#ARG ARG_KEYSPACES_ENPOINT=cassandra.us-east-1.amazonaws.com
12+
13+
# Make it available at runtime via ENV
14+
# ENV KEYSPACES_ENPOINT=${ARG_KEYSPACES_ENPOINT
15+
16+
COPY entrypoint.sh /entrypoint.sh
17+
18+
RUN chmod +x /entrypoint.sh
19+
20+
ENTRYPOINT ["/entrypoint.sh"]

migration/online/zdm-proxy/README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# 🚀 ZDM Dual Write Proxy for Amazon Keyspaces Migration
2+
3+
This project extends the official [ ZDM Proxy](https://github.com/datastax/zdm-proxy) to support seamless **zero-downtime migration** from **Apache Cassandra** to **Amazon Keyspaces (for Apache Cassandra)** with AWS best practices.
4+
5+
It introduces key enhancements:
6+
7+
- A custom Docker image hosted in **Amazon ECR** for VPC-accessible deployments.
8+
- A **CloudFormation template** to deploy the proxy on **AWS Fargate**, ensuring a scalable, serverless, and secure setup within your existing AWS infrastructure.
9+
10+
11+
The proxy is deployed with Amazon ECS on Fargate which can scale up and down based on application demand. The Network load balancer allows application traffic to be distributed across a number of ECS tasks.
12+
13+
![this screenshot](/aws-ecs-zdm.drawio.png)
14+
15+
16+
17+
## 📁 Project Structure
18+
19+
| File | Description |
20+
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
21+
| `Dockerfile` | Builds the custom ZDM Proxy image with Keyspaces-compatible networking and TLS support. |
22+
| `entrypoint.sh` | Entry script for the container. Resolves DNS, manages proxy routing via `socat`, and sets environment variables dynamically. |
23+
| `move-docker-to-ecr.sh` | Automates Docker image build, tagging, and pushing to Amazon ECR. Also downloads the required TLS root cert. |
24+
| `zdm-proxy-cloudformation.yaml` | CloudFormation template for deploying the proxy as a Fargate task behind an NLB in a private VPC. |
25+
26+
---
27+
28+
## 🛠️ Parameters (CloudFormation Template)
29+
30+
### 🔌 Network Configuration
31+
32+
- **VPCId**: ID of your target VPC.
33+
- **PrivateSubnetIds**: List of private subnet IDs.
34+
- **SecurityGroupId**: Security Group for the Network Load Balancer.
35+
- **RouteTableId**: Optional; for route management if using PrivateLink.
36+
37+
### 🔄 Origin & Target Cassandra Config
38+
39+
- **ZDMOriginContactPoints**, **ZDMTargetContactPoints**: IP/DNS for the clusters.
40+
- **ZDMOriginPort**, **ZDMTargetPort**: Usually 9042 for Cassandra, 9142 for Amazon Keyspaces.
41+
- **ZDMOriginUsername/Password**, **ZDMTargetUsername/Password**: Auth credentials.
42+
43+
### ⚙️ Proxy Config
44+
45+
- **ServiceReplicaCount**: Number of ECS tasks to launch.
46+
- **ZDMProxyPort**: Port for the proxy service. Default is `14002`.
47+
48+
---
49+
50+
## 📦 Deployment Instructions
51+
52+
### 1. 🧱 Build and Push Image
53+
54+
```bash
55+
./move-docker-to-ecr.sh
56+
```
57+
58+
### 2. ☁️ Launch CloudFormation Stack
59+
60+
Upload the `zdm-proxy-cloudformation.yaml` to S3 or the AWS Console and deploy it. Provide required parameters (e.g., subnets, contact points).
61+
62+
---
63+
64+
## 🔐 Security and TLS
65+
66+
- TLS is handled via Amazon Keyspaces' default requirement. The proxy ensures secure, in-transit communication.
67+
68+
---
69+
70+
## ✅ Best Practices for Amazon Keyspaces
71+
72+
- Uses **port 9142** for CQL over TLS as required by Amazon Keyspaces.
73+
- Supports **DNS-based discovery** of Amazon Keyspaces via `entrypoint.sh`.
74+
- Deployable **entirely within a VPC** for added security and compliance.
75+
76+
---
77+
78+
## 🧪 Testing & Validation
79+
80+
Once deployed:
81+
82+
- Point your application to the NLB DNS created by the CloudFormation stack.
83+
- Test dual writes by verifying data in both origin and target clusters.
84+
85+
---
86+
87+
## 📚 References
88+
89+
- [Amazon Keyspaces Developer Guide](https://docs.aws.amazon.com/keyspaces/latest/devguide/)
90+
- [Official ZDM Proxy Repo](https://github.com/datastax/zdm-proxy)
91+
Loading
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/bin/sh
2+
set -ex
3+
4+
# Resolve Cassandra IP
5+
REGION=${AWS_REGION:-"us-east-1"}
6+
ENPOINT="cassandra.${REGION}.amazonaws.com"
7+
CASSANDRA_PORT=9142
8+
9+
CASSANDRA_IP=$(dig +short "$ENPOINT" | head -n 1)
10+
11+
if [ -z "$CASSANDRA_IP" ]; then
12+
echo "❌ Failed to resolve $ENPOINT"
13+
exit 1
14+
fi
15+
16+
echo "✅ Resolved $ENPOINT$CASSANDRA_IP"
17+
18+
# Start local proxy (e.g. socat)
19+
socat TCP-LISTEN:9142,bind=127.0.0.1,reuseaddr,fork TCP:$CASSANDRA_IP:$CASSANDRA_PORT &
20+
21+
if [ -n "$AWS_NLB_DNS" ]; then
22+
echo "✅ AWS_NLB_DNS is set: $AWS_NLB_DNS"
23+
24+
# Read IPs into a space-separated string
25+
ALL_IPS=$(dig +short "$AWS_NLB_DNS" | tr '\n' ' ')
26+
27+
if [ -n "$ALL_IPS" ]; then
28+
echo "AWS_NLB_DNS IPs found: $ALL_IPS"
29+
30+
# Convert space-separated list to comma-separated
31+
ZDM_PROXY_TOPOLOGY_ADDRESSES=$(echo "$ALL_IPS" | sed 's/ /, /g' | sed 's/, $//')
32+
33+
export ZDM_PROXY_TOPOLOGY_ADDRESSES
34+
35+
# Count IPs (words in string)
36+
IP_COUNT=$(echo "$ALL_IPS" | wc -w)
37+
38+
# Generate a random number 0 <= n < IP_COUNT
39+
RAND=$(awk -v max="$IP_COUNT" 'BEGIN { srand(); print int(rand() * max) }')
40+
41+
# Get selected IP
42+
SELECTED_IP=$(echo "$ALL_IPS" | awk -v n=$((RAND + 1)) '{ print $n }')
43+
44+
export ZDM_PROXY_TOPOLOGY_INDEX=$RAND
45+
export ZDM_PROXY_TOPOLOGY_ADDRESS=$SELECTED_IP
46+
47+
echo "ZDM_PROXY_TOPOLOGY_INDEX: $ZDM_PROXY_TOPOLOGY_INDEX"
48+
echo "ZDM_PROXY_TOPOLOGY_ADDRESS: $ZDM_PROXY_TOPOLOGY_ADDRESS"
49+
else
50+
echo "AWS_NLB_DNS No IPs returned."
51+
fi
52+
else
53+
echo "❌ AWS_NLB_DNS is not set"
54+
fi
55+
56+
# Hand off to ZDM proxy main binary
57+
exec /main "$@"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
3+
set -e # Exit immediately if any command fails
4+
5+
REGION="us-east-1"
6+
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
7+
DOCKER_REPO_NAME="datastax/zdm-proxy"
8+
AWS_REPO_NAME="zdm-proxy"
9+
IMAGE_TAG="latest"
10+
11+
12+
# Authenticate with ECR
13+
aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com
14+
15+
# Create ECR repository (ignore error if it already exists)
16+
aws ecr create-repository --repository-name ${AWS_REPO_NAME} --region ${REGION} || echo "ECR repo already exists"
17+
18+
# Pull image from Docker Hub
19+
docker pull --platform linux/amd64 ${DOCKER_REPO_NAME}:${IMAGE_TAG}
20+
21+
mkdir -p certs
22+
23+
curl -o certs/sf-class2-root.crt https://certs.secureserver.net/repository/sf-class2-root.crt
24+
25+
# Build locally
26+
docker build -f Dockerfile --platform linux/amd64 -t zdm-proxy-custom:latest .
27+
28+
docker tag zdm-proxy-custom:${IMAGE_TAG} ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/zdm-proxy:latest
29+
30+
# Push to ECR
31+
docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${AWS_REPO_NAME}:${IMAGE_TAG}

0 commit comments

Comments
 (0)