Service Discovery Stack β
Overview β
The Service Discovery Stack provides service mesh capabilities, secrets management, and service registry for the HealthFlow NDP platform using HashiCorp Consul and Vault.
Stack Architecture β
Components β
1. HashiCorp Consul β
Purpose: Service discovery, health checking, and service mesh
Version: Consul 1.18.x
Features:
- Service Discovery: Automatic service registration and DNS-based discovery
- Health Checking: Built-in health checks for services
- KV Store: Distributed key-value store for configuration
- Service Mesh: Secure service-to-service communication with Consul Connect
- Multi-datacenter: Support for federation across data centers
Use Cases:
- Service registration and discovery
- Configuration management
- Distributed locks
- Service segmentation
- Health monitoring
2. HashiCorp Vault β
Purpose: Secrets management and data protection
Version: Vault 1.16.x
Features:
- Dynamic Secrets: Generate secrets on-demand with automatic rotation
- Data Encryption: Encrypt data in transit and at rest
- Leasing & Renewal: All secrets have a lease with automatic renewal
- Revocation: Secrets can be revoked immediately
- Audit Logging: Detailed audit trail of all operations
- High Availability: Support for HA deployment with Consul backend
Secrets Managed:
- Database credentials (PostgreSQL, MySQL, MongoDB, Redis)
- API keys and tokens
- TLS certificates
- Encryption keys
- Cloud provider credentials
- Third-party service credentials (EDA, MOH, Insurance)
3. Consul Agents β
Purpose: Local service mesh proxy and health checking
Deployment: DaemonSet (runs on all nodes)
Features:
- Local DNS interface for service discovery
- Health check execution
- Service registration
- Sidecar proxy for service mesh
Kubernetes Manifests β
Namespace β
yaml
apiVersion: v1
kind: Namespace
metadata:
name: discovery-stack
labels:
name: discovery-stack
stack: infrastructureConsul StatefulSet β
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: consul
namespace: discovery-stack
spec:
serviceName: consul
replicas: 3
selector:
matchLabels:
app: consul
template:
metadata:
labels:
app: consul
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- consul
topologyKey: kubernetes.io/hostname
containers:
- name: consul
image: hashicorp/consul:1.18
args:
- "agent"
- "-server"
- "-bootstrap-expect=3"
- "-ui"
- "-data-dir=/consul/data"
- "-bind=0.0.0.0"
- "-client=0.0.0.0"
- "-advertise=$(POD_IP)"
- "-retry-join=consul-0.consul.discovery-stack.svc.cluster.local"
- "-retry-join=consul-1.consul.discovery-stack.svc.cluster.local"
- "-retry-join=consul-2.consul.discovery-stack.svc.cluster.local"
- "-domain=cluster.local"
- "-datacenter=healthflow-dc1"
- "-disable-host-node-id"
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 8500
name: http
- containerPort: 8600
name: dns
- containerPort: 8300
name: server
- containerPort: 8301
name: serf-lan
- containerPort: 8302
name: serf-wan
volumeMounts:
- name: consul-data
mountPath: /consul/data
resources:
limits:
cpu: "1"
memory: 2Gi
requests:
cpu: "500m"
memory: 1Gi
livenessProbe:
httpGet:
path: /v1/status/leader
port: 8500
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /v1/status/peers
port: 8500
initialDelaySeconds: 10
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: consul-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3
resources:
requests:
storage: 20GiConsul Service β
yaml
apiVersion: v1
kind: Service
metadata:
name: consul
namespace: discovery-stack
labels:
app: consul
spec:
type: ClusterIP
clusterIP: None
selector:
app: consul
ports:
- name: http
port: 8500
targetPort: 8500
- name: dns
port: 8600
targetPort: 8600
- name: server
port: 8300
targetPort: 8300
- name: serf-lan-tcp
port: 8301
targetPort: 8301
protocol: TCP
- name: serf-lan-udp
port: 8301
targetPort: 8301
protocol: UDPConsul UI Service β
yaml
apiVersion: v1
kind: Service
metadata:
name: consul-ui
namespace: discovery-stack
spec:
type: ClusterIP
selector:
app: consul
ports:
- name: http
port: 8500
targetPort: 8500Vault StatefulSet β
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vault
namespace: discovery-stack
spec:
serviceName: vault
replicas: 3
selector:
matchLabels:
app: vault
template:
metadata:
labels:
app: vault
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- vault
topologyKey: kubernetes.io/hostname
containers:
- name: vault
image: hashicorp/vault:1.16
args:
- "server"
env:
- name: VAULT_ADDR
value: "http://127.0.0.1:8200"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: VAULT_API_ADDR
value: "http://$(POD_IP):8200"
- name: VAULT_CLUSTER_ADDR
value: "https://$(POD_IP):8201"
ports:
- containerPort: 8200
name: http
- containerPort: 8201
name: internal
volumeMounts:
- name: config
mountPath: /vault/config
- name: vault-data
mountPath: /vault/data
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: "500m"
memory: 512Mi
securityContext:
capabilities:
add:
- IPC_LOCK
livenessProbe:
httpGet:
path: /v1/sys/health?standbyok=true
port: 8200
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /v1/sys/health?standbyok=true&sealedcode=204&uninitcode=204
port: 8200
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: config
configMap:
name: vault-config
volumeClaimTemplates:
- metadata:
name: vault-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3
resources:
requests:
storage: 10GiVault ConfigMap β
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: vault-config
namespace: discovery-stack
data:
vault.hcl: |
ui = true
storage "consul" {
address = "consul:8500"
path = "vault/"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
api_addr = "http://POD_IP:8200"
cluster_addr = "https://POD_IP:8201"
disable_mlock = falseVault Service β
yaml
apiVersion: v1
kind: Service
metadata:
name: vault
namespace: discovery-stack
spec:
type: ClusterIP
selector:
app: vault
ports:
- name: http
port: 8200
targetPort: 8200
- name: internal
port: 8201
targetPort: 8201Service Dependencies β
Deployment Instructions β
1. Create Namespace β
bash
kubectl create namespace discovery-stack2. Deploy Consul β
bash
# Deploy Consul StatefulSet
kubectl apply -f consul/statefulset.yaml
# Deploy Consul Services
kubectl apply -f consul/service.yaml
kubectl apply -f consul/service-ui.yaml
# Wait for Consul to be ready
kubectl wait --for=condition=ready pod -l app=consul -n discovery-stack --timeout=300s3. Verify Consul β
bash
# Check Consul pods
kubectl get pods -n discovery-stack -l app=consul
# Check Consul cluster members
kubectl exec -n discovery-stack consul-0 -- consul members
# Access Consul UI (port-forward)
kubectl port-forward -n discovery-stack svc/consul-ui 8500:8500
# Visit http://localhost:85004. Deploy Vault β
bash
# Create Vault config
kubectl apply -f vault/configmap.yaml
# Deploy Vault StatefulSet
kubectl apply -f vault/statefulset.yaml
# Deploy Vault Service
kubectl apply -f vault/service.yaml
# Wait for Vault pods
kubectl wait --for=condition=ready pod -l app=vault -n discovery-stack --timeout=300s5. Initialize Vault β
bash
# Initialize Vault (run once)
kubectl exec -n discovery-stack vault-0 -- vault operator init -format=json > vault-keys.json
# IMPORTANT: Save vault-keys.json securely! It contains unseal keys and root token
# Extract unseal keys
UNSEAL_KEY_1=$(cat vault-keys.json | jq -r '.unseal_keys_b64[0]')
UNSEAL_KEY_2=$(cat vault-keys.json | jq -r '.unseal_keys_b64[1]')
UNSEAL_KEY_3=$(cat vault-keys.json | jq -r '.unseal_keys_b64[2]')
ROOT_TOKEN=$(cat vault-keys.json | jq -r '.root_token')
# Unseal Vault on all pods
for pod in vault-0 vault-1 vault-2; do
kubectl exec -n discovery-stack $pod -- vault operator unseal $UNSEAL_KEY_1
kubectl exec -n discovery-stack $pod -- vault operator unseal $UNSEAL_KEY_2
kubectl exec -n discovery-stack $pod -- vault operator unseal $UNSEAL_KEY_3
done
# Login to Vault
kubectl exec -n discovery-stack vault-0 -- vault login $ROOT_TOKEN6. Configure Vault β
bash
# Enable secrets engines
kubectl exec -n discovery-stack vault-0 -- vault secrets enable -path=secret kv-v2
kubectl exec -n discovery-stack vault-0 -- vault secrets enable database
# Enable authentication methods
kubectl exec -n discovery-stack vault-0 -- vault auth enable kubernetes
# Configure Kubernetes auth
kubectl exec -n discovery-stack vault-0 -- vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc.cluster.local:443"Vault Secrets Configuration β
Database Secrets Engine β
bash
# Configure PostgreSQL dynamic secrets
kubectl exec -n discovery-stack vault-0 -- vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
allowed_roles="ndp-app" \
connection_url="postgresql://{{username}}:{{password}}@postgresql.data-stack:5432/ndp?sslmode=disable" \
username="vault" \
password="vault-password"
# Create role for dynamic credentials
kubectl exec -n discovery-stack vault-0 -- vault write database/roles/ndp-app \
db_name=postgresql \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"Static Secrets β
bash
# Store static secrets
kubectl exec -n discovery-stack vault-0 -- vault kv put secret/ndp/api \
eda_api_key="eda-secret-key" \
moh_api_key="moh-secret-key"
# Store Redis password
kubectl exec -n discovery-stack vault-0 -- vault kv put secret/ndp/redis \
password="redis-password"Application Access Policy β
bash
# Create policy for NDP applications
kubectl exec -n discovery-stack vault-0 -- vault policy write ndp-app - <<EOF
# Read application secrets
path "secret/data/ndp/*" {
capabilities = ["read", "list"]
}
# Generate database credentials
path "database/creds/ndp-app" {
capabilities = ["read"]
}
EOF
# Bind policy to Kubernetes service account
kubectl exec -n discovery-stack vault-0 -- vault write auth/kubernetes/role/ndp-app \
bound_service_account_names=prescription-service,dispense-service \
bound_service_account_namespaces=applications \
policies=ndp-app \
ttl=1hConsul Service Registration β
Manual Service Registration β
bash
# Register a service in Consul
kubectl exec -n discovery-stack consul-0 -- consul services register - <<EOF
{
"Name": "postgresql",
"Address": "postgresql.data-stack.svc.cluster.local",
"Port": 5432,
"Tags": ["database", "postgres"],
"Check": {
"TCP": "postgresql.data-stack.svc.cluster.local:5432",
"Interval": "10s"
}
}
EOFAutomatic Service Registration β
Applications can register themselves using Consul's Kubernetes sync:
yaml
apiVersion: v1
kind: Service
metadata:
name: prescription-service
namespace: applications
annotations:
"consul.hashicorp.com/service-name": "prescription-service"
"consul.hashicorp.com/service-tags": "ndp,api,v1"
"consul.hashicorp.com/service-port": "8080"
spec:
selector:
app: prescription-service
ports:
- port: 8080
targetPort: 8080Resource Requirements β
Estimates
These are rough estimates for development/staging. Production should be scaled based on load.
| Service | CPU | Memory | Storage | Replicas | Notes |
|---|---|---|---|---|---|
| Consul | 0.5 core | 1 GB | 20 GB | 3 | HA cluster |
| Vault | 0.5 core | 512 MB | 10 GB | 3 | HA cluster |
| Consul Agent | 0.1 core | 128 MB | - | Per node | DaemonSet |
Best Practices β
Vault Security β
- Never commit unseal keys to Git
- Use auto-unseal in production (Hardware HSM, Transit secrets engine, or local key management)
- Enable audit logging
- Rotate root token regularly
- Use namespaces for multi-tenancy
- Enable MFA for sensitive operations
Consul Configuration β
- Enable ACLs in production
- Use TLS for all communication
- Implement intentions for service mesh security
- Monitor cluster health
- Backup Consul data regularly
Secret Management β
- Use dynamic secrets whenever possible
- Set appropriate TTL for leases
- Implement secret rotation
- Use Vault agent for secret injection
- Audit secret access
Troubleshooting β
Vault is Sealed β
bash
# Check Vault status
kubectl exec -n discovery-stack vault-0 -- vault status
# Unseal Vault
kubectl exec -n discovery-stack vault-0 -- vault operator unseal <unseal-key>Consul Cluster Not Forming β
bash
# Check Consul logs
kubectl logs -n discovery-stack consul-0
# Check cluster members
kubectl exec -n discovery-stack consul-0 -- consul members
# Check Consul peers
kubectl exec -n discovery-stack consul-0 -- consul operator raft list-peersApplication Can't Access Vault β
bash
# Test Kubernetes auth
kubectl exec -n applications prescription-service-xxx -- \
curl -s http://vault.discovery-stack:8200/v1/auth/kubernetes/login \
-d '{"jwt":"'$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)'","role":"ndp-app"}'
# Check Vault logs
kubectl logs -n discovery-stack vault-0
# Verify policy
kubectl exec -n discovery-stack vault-0 -- vault policy read ndp-appService Not Registering in Consul β
bash
# Check Consul catalog
kubectl exec -n discovery-stack consul-0 -- consul catalog services
# Check service health
kubectl exec -n discovery-stack consul-0 -- consul catalog nodes -service=<service-name>
# Check Consul agent logs
kubectl logs -n discovery-stack -l app=consul-agentSecurity Considerations β
Vault β
- Seal Configuration: Use auto-unseal in production
- Access Control: Implement strict policies
- Audit Logging: Enable and monitor audit logs
- Encryption: Enable transit secrets engine for app-level encryption
- Backup: Regular snapshots of Vault data
Consul β
- ACL System: Enable and configure ACL tokens
- TLS Encryption: Encrypt all Consul communication
- Intentions: Define service-to-service communication rules
- Gossip Encryption: Enable gossip protocol encryption
- Network Segmentation: Use Consul Enterprise namespaces
High Availability β
Consul HA β
- 3 or 5 server nodes for quorum
- Anti-affinity rules to spread across availability zones
- Health checks on all endpoints
- Automated backups of Consul state
Vault HA β
- 3 or 5 Vault instances with Consul backend
- Load balancer for active/standby routing
- Auto-unseal for automatic recovery
- Regular snapshots of Consul backend
Next Steps β
- Application Stack - Deploy NDP services
- Gateway Stack - Configure ingress for Consul/Vault UI
- Monitoring Stack - Monitor Consul and Vault