diff --git a/helm/api7ee/README.md b/helm/api7ee/README.md index 40c633c..4bd0f9a 100644 --- a/helm/api7ee/README.md +++ b/helm/api7ee/README.md @@ -33,6 +33,16 @@ helm install my-api7ee ./helm/api7ee -f custom-values.yaml ## Configuration +### API7 Gateway Integration + +This Helm chart includes automatic API7 Gateway configuration using ADC (API7 Declarative CLI). When `api7.enabled` is set to `true`, the chart will: + +1. **Deploy ADC Configuration**: Creates routes, services, and upstreams for your applications +2. **Configure TLS/SSL**: Manages certificates via cert-manager or custom certificates +3. **Enable Service Discovery**: Uses Kubernetes native service discovery +4. **Apply Security Policies**: Configures rate limiting, CORS, and authentication +5. **Auto-publish Routes**: Optionally publishes routes automatically after deployment + ### Key Configuration Options | Parameter | Description | Default | @@ -49,9 +59,41 @@ helm install my-api7ee ./helm/api7ee -f custom-values.yaml | `api.service.port` | API service port | `8080` | | `ingress.enabled` | Enable ingress | `true` | | `ingress.hosts[0].host` | Ingress hostname | `demo.commandware.it` | +| `api7.enabled` | Enable API7 ADC configuration | `true` | +| `api7.gateway.adminUrl` | API7 Gateway Admin API URL | `http://api7-gateway.api7ee:9180` | +| `api7.hosts` | Hosts for API7 routing | `[demo.commandware.it]` | +| `api7.tls.certManager.enabled` | Use cert-manager for TLS | `true` | +| `api7.autoPublish` | Auto-publish routes | `true` | ### Custom Values Examples +#### Configure API7 Gateway: + +```yaml +api7: + enabled: true + gateway: + adminUrl: http://your-api7-gateway:9180 + adminKey: "your-admin-key-here" + group: production + hosts: + - api.yourdomain.com + tls: + certManager: + enabled: true + issuer: letsencrypt-prod + plugins: + rateLimit: + enabled: true + count: 1000 + timeWindow: 60 + auth: + enabled: true + consumers: + - username: api-client + apiKey: secure-api-key-12345 +``` + #### Using a private registry: ```yaml @@ -119,6 +161,31 @@ metrics: ## Troubleshooting +### API7 ADC Sync Issues + +If the ADC sync job fails: + +```bash +# Check the job status +kubectl get jobs -l app.kubernetes.io/instance=my-api7ee + +# View job logs +kubectl logs job/my-api7ee-adc-sync + +# Manually run ADC sync +kubectl run adc-debug --rm -it --image=ghcr.io/api7/adc:latest -- /bin/sh +``` + +### Verify API7 Configuration + +```bash +# Check if routes are configured +curl -H "X-API-KEY: your-admin-key" http://api7-gateway:9180/apisix/admin/routes + +# Check service discovery +curl -H "X-API-KEY: your-admin-key" http://api7-gateway:9180/apisix/admin/upstreams +``` + ### Check deployment status: ```bash kubectl get deployments -l app.kubernetes.io/instance=my-api7ee diff --git a/helm/api7ee/templates/certificate.yaml b/helm/api7ee/templates/certificate.yaml new file mode 100644 index 0000000..5bec334 --- /dev/null +++ b/helm/api7ee/templates/certificate.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.api7.enabled .Values.api7.tls.enabled .Values.api7.tls.certManager.enabled }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "api7ee.fullname" . }}-tls + labels: + {{- include "api7ee.labels" . | nindent 4 }} +spec: + secretName: {{ .Values.api7.tls.secretName | default (printf "%s-tls" (include "api7ee.fullname" .)) }} + issuerRef: + name: {{ .Values.api7.tls.certManager.issuer }} + kind: {{ .Values.api7.tls.certManager.issuerKind | default "ClusterIssuer" }} + commonName: {{ first .Values.api7.hosts }} + dnsNames: + {{- range .Values.api7.hosts }} + - {{ . | quote }} + {{- end }} + usages: + - digital signature + - key encipherment + - server auth + - client auth + duration: 2160h # 90 days + renewBefore: 720h # 30 days before expiry +{{- end }} \ No newline at end of file diff --git a/helm/api7ee/templates/configmap-adc.yaml b/helm/api7ee/templates/configmap-adc.yaml new file mode 100644 index 0000000..d78e8ed --- /dev/null +++ b/helm/api7ee/templates/configmap-adc.yaml @@ -0,0 +1,147 @@ +{{- if .Values.api7.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "api7ee.fullname" . }}-adc-config + labels: + {{- include "api7ee.labels" . | nindent 4 }} + app.kubernetes.io/component: adc +data: + adc-config.yaml: | + services: + {{- if .Values.web.enabled }} + - name: web-service + upstream: + name: web-upstream + scheme: http + type: roundrobin + {{- if .Values.api7.serviceDiscovery.enabled }} + discovery_type: kubernetes + service_name: {{ .Release.Namespace }}/{{ include "api7ee.fullname" . }}-web:http + {{- else }} + nodes: + - host: {{ include "api7ee.fullname" . }}-web.{{ .Release.Namespace }}.svc.cluster.local + port: {{ .Values.web.service.port }} + weight: 100 + {{- end }} + routes: + - name: web-route + uris: + - /* + hosts: + {{- range .Values.api7.hosts }} + - {{ . | quote }} + {{- end }} + priority: 0 + plugins: + {{- if .Values.api7.tls.enabled }} + redirect: + http_to_https: true + {{- end }} + {{- if .Values.api7.plugins.rateLimit.enabled }} + limit-count: + count: {{ .Values.api7.plugins.rateLimit.count }} + time_window: {{ .Values.api7.plugins.rateLimit.timeWindow }} + rejected_code: 429 + {{- end }} + {{- if .Values.api7.plugins.cors.enabled }} + cors: + allow_origins: {{ .Values.api7.plugins.cors.allowOrigins | toJson }} + allow_methods: {{ .Values.api7.plugins.cors.allowMethods | toJson }} + allow_headers: {{ .Values.api7.plugins.cors.allowHeaders | toJson }} + expose_headers: {{ .Values.api7.plugins.cors.exposeHeaders | toJson }} + max_age: {{ .Values.api7.plugins.cors.maxAge }} + allow_credentials: {{ .Values.api7.plugins.cors.allowCredentials }} + {{- end }} + {{- end }} + + {{- if .Values.api.enabled }} + - name: api-service + upstream: + name: api-upstream + scheme: http + type: roundrobin + {{- if .Values.api7.serviceDiscovery.enabled }} + discovery_type: kubernetes + service_name: {{ .Release.Namespace }}/{{ include "api7ee.fullname" . }}-api:http + {{- else }} + nodes: + - host: {{ include "api7ee.fullname" . }}-api.{{ .Release.Namespace }}.svc.cluster.local + port: {{ .Values.api.service.port }} + weight: 100 + {{- end }} + routes: + - name: api-route + uris: + - /api + - /api/* + hosts: + {{- range .Values.api7.hosts }} + - {{ . | quote }} + {{- end }} + priority: 10 + plugins: + {{- if .Values.api7.tls.enabled }} + redirect: + http_to_https: true + {{- end }} + proxy-rewrite: + regex_uri: + - ^/api/(.*) + - /$1 + {{- if .Values.api7.plugins.rateLimit.enabled }} + limit-count: + count: {{ .Values.api7.plugins.rateLimit.apiCount | default .Values.api7.plugins.rateLimit.count }} + time_window: {{ .Values.api7.plugins.rateLimit.timeWindow }} + rejected_code: 429 + {{- end }} + {{- if .Values.api7.plugins.auth.enabled }} + key-auth: + header: {{ .Values.api7.plugins.auth.header | default "X-API-Key" }} + {{- end }} + {{- end }} + + {{- if .Values.api7.tls.enabled }} + ssls: + - snis: + {{- range .Values.api7.hosts }} + - {{ . | quote }} + {{- end }} + certificates: + {{- if .Values.api7.tls.certManager.enabled }} + - certificate: /etc/ssl/certs/tls.crt + key: /etc/ssl/certs/tls.key + {{- else if .Values.api7.tls.certificate }} + - certificate: | + {{ .Values.api7.tls.certificate | nindent 14 }} + key: | + {{ .Values.api7.tls.key | nindent 14 }} + {{- end }} + {{- end }} + + {{- if .Values.api7.plugins.auth.enabled }} + consumers: + {{- range .Values.api7.consumers }} + - username: {{ .username }} + plugins: + key-auth: + key: {{ .apiKey }} + {{- end }} + {{- end }} + + global_rules: + {{- if .Values.api7.plugins.prometheus.enabled }} + - id: prometheus-metrics + plugins: + prometheus: + prefer_name: true + {{- end }} + {{- if .Values.api7.plugins.logging.enabled }} + - id: request-logging + plugins: + http-logger: + uri: {{ .Values.api7.plugins.logging.endpoint }} + batch_max_size: {{ .Values.api7.plugins.logging.batchMaxSize | default 1000 }} + inactive_timeout: {{ .Values.api7.plugins.logging.inactiveTimeout | default 5 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/api7ee/templates/job-adc-sync.yaml b/helm/api7ee/templates/job-adc-sync.yaml new file mode 100644 index 0000000..cfd0964 --- /dev/null +++ b/helm/api7ee/templates/job-adc-sync.yaml @@ -0,0 +1,212 @@ +{{- if .Values.api7.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "api7ee.fullname" . }}-adc-sync + labels: + {{- include "api7ee.labels" . | nindent 4 }} + app.kubernetes.io/component: adc-sync + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "10" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + backoffLimit: 3 + activeDeadlineSeconds: 300 + template: + metadata: + labels: + {{- include "api7ee.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: adc-sync + spec: + restartPolicy: Never + serviceAccountName: {{ include "api7ee.serviceAccountName" . }} + {{- if .Values.api7.tls.certManager.enabled }} + initContainers: + - name: wait-for-certificate + image: busybox:1.35 + command: + - sh + - -c + - | + echo "Waiting for TLS certificate to be ready..." + while [ ! -f /etc/ssl/certs/tls.crt ] || [ ! -f /etc/ssl/certs/tls.key ]; do + echo "Certificate not ready, waiting..." + sleep 5 + done + echo "Certificate is ready!" + volumeMounts: + - name: tls-certs + mountPath: /etc/ssl/certs + readOnly: true + {{- end }} + containers: + - name: adc-sync + image: {{ .Values.api7.adc.image | default "ghcr.io/api7/adc:latest" }} + imagePullPolicy: {{ .Values.api7.adc.imagePullPolicy | default "IfNotPresent" }} + command: + - /bin/sh + - -c + - | + set -e + echo "Starting API7 ADC configuration sync..." + + # Install jq if needed for auto-publish feature + {{- if .Values.api7.autoPublish }} + if ! command -v jq &> /dev/null; then + echo "Installing jq..." + apk add --no-cache jq curl || apt-get update && apt-get install -y jq curl || yum install -y jq curl + fi + {{- end }} + + # Wait for API7 Gateway to be ready + echo "Waiting for API7 Gateway to be available..." + MAX_RETRIES=30 + RETRY_COUNT=0 + while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + if curl -s -o /dev/null -w "%{http_code}" ${API7_ADMIN_URL}/apisix/admin/routes \ + -H "X-API-KEY: ${API7_ADMIN_KEY}" | grep -q "200\|401"; then + echo "API7 Gateway is ready!" + break + fi + echo "API7 Gateway not ready, retrying... ($RETRY_COUNT/$MAX_RETRIES)" + RETRY_COUNT=$((RETRY_COUNT + 1)) + sleep 10 + done + + if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then + echo "ERROR: API7 Gateway not ready after $MAX_RETRIES attempts" + exit 1 + fi + + {{- if .Values.api7.tls.certManager.enabled }} + # Copy certificates to config directory + cp /etc/ssl/certs/tls.crt /tmp/tls.crt + cp /etc/ssl/certs/tls.key /tmp/tls.key + + # Update certificate paths in config + sed -i 's|/etc/ssl/certs/tls.crt|/tmp/tls.crt|g' /config/adc-config.yaml + sed -i 's|/etc/ssl/certs/tls.key|/tmp/tls.key|g' /config/adc-config.yaml + {{- end }} + + # Validate configuration + echo "Validating ADC configuration..." + adc validate -f /config/adc-config.yaml || { + echo "ERROR: Configuration validation failed" + cat /config/adc-config.yaml + exit 1 + } + + # Sync configuration to API7 + echo "Syncing configuration to API7 Gateway..." + adc sync -f /config/adc-config.yaml \ + --backend {{ .Values.api7.backend | default "api7ee" }} \ + --server ${API7_ADMIN_URL} \ + --token ${API7_ADMIN_KEY} \ + --gateway-group ${API7_GATEWAY_GROUP} \ + {{- if .Values.api7.adc.tlsSkipVerify }} + --tls-skip-verify \ + {{- end }} + --verbose || { + echo "ERROR: Failed to sync configuration" + exit 1 + } + + echo "✅ API7 configuration sync completed successfully!" + + {{- if .Values.api7.autoPublish }} + # Auto-publish routes + echo "Auto-publishing routes..." + + # Get list of services and routes + SERVICES=$(curl -s ${API7_ADMIN_URL}/apisix/admin/services \ + -H "X-API-KEY: ${API7_ADMIN_KEY}" | jq -r '.list[].id' || echo "") + + for SERVICE_ID in $SERVICES; do + echo "Publishing routes for service: $SERVICE_ID" + + # Get routes for this service + ROUTES=$(curl -s ${API7_ADMIN_URL}/apisix/admin/services/${SERVICE_ID}/routes \ + -H "X-API-KEY: ${API7_ADMIN_KEY}" | jq -r '.list[].id' || echo "") + + for ROUTE_ID in $ROUTES; do + echo "Publishing route: $ROUTE_ID" + curl -X POST ${API7_ADMIN_URL}/apisix/admin/services/${SERVICE_ID}/routes/${ROUTE_ID}/publish \ + -H "X-API-KEY: ${API7_ADMIN_KEY}" \ + -H "Content-Type: application/json" \ + -d "{\"gateway_group_id\": \"${API7_GATEWAY_GROUP}\"}" || { + echo "Warning: Failed to publish route $ROUTE_ID" + } + done + done + + echo "✅ Routes published successfully!" + {{- end }} + + # Display summary + echo "" + echo "==========================================" + echo "API7 Configuration Summary:" + echo "==========================================" + echo "Gateway URL: ${API7_ADMIN_URL}" + echo "Gateway Group: ${API7_GATEWAY_GROUP}" + echo "Hosts configured:" + {{- range .Values.api7.hosts }} + echo " - {{ . }}" + {{- end }} + {{- if .Values.api7.tls.enabled }} + echo "TLS: Enabled" + {{- end }} + {{- if .Values.api7.serviceDiscovery.enabled }} + echo "Service Discovery: Kubernetes" + {{- end }} + echo "==========================================" + echo "" + echo "Access your application at:" + {{- range .Values.api7.hosts }} + echo " {{ if $.Values.api7.tls.enabled }}https{{ else }}http{{ end }}://{{ . }}" + {{- end }} + env: + - name: ADC_VERBOSE + value: "{{ .Values.api7.adc.verbose | default true }}" + - name: API7_ADMIN_URL + valueFrom: + secretKeyRef: + name: {{ include "api7ee.fullname" . }}-api7-admin + key: admin-url + - name: API7_ADMIN_KEY + valueFrom: + secretKeyRef: + name: {{ include "api7ee.fullname" . }}-api7-admin + key: admin-key + - name: API7_GATEWAY_GROUP + valueFrom: + secretKeyRef: + name: {{ include "api7ee.fullname" . }}-api7-admin + key: gateway-group + volumeMounts: + - name: adc-config + mountPath: /config + readOnly: true + {{- if .Values.api7.tls.certManager.enabled }} + - name: tls-certs + mountPath: /etc/ssl/certs + readOnly: true + {{- end }} + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + volumes: + - name: adc-config + configMap: + name: {{ include "api7ee.fullname" . }}-adc-config + {{- if .Values.api7.tls.certManager.enabled }} + - name: tls-certs + secret: + secretName: {{ .Values.api7.tls.secretName | default (printf "%s-tls" (include "api7ee.fullname" .)) }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/api7ee/templates/rbac-adc.yaml b/helm/api7ee/templates/rbac-adc.yaml new file mode 100644 index 0000000..76cc848 --- /dev/null +++ b/helm/api7ee/templates/rbac-adc.yaml @@ -0,0 +1,36 @@ +{{- if and .Values.api7.enabled .Values.serviceAccount.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "api7ee.fullname" . }}-adc + labels: + {{- include "api7ee.labels" . | nindent 4 }} +rules: + # Allow reading secrets (for certificates) + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] + # Allow reading services and endpoints for service discovery + - apiGroups: [""] + resources: ["services", "endpoints"] + verbs: ["get", "list", "watch"] + # Allow reading pods for health checks + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "api7ee.fullname" . }}-adc + labels: + {{- include "api7ee.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "api7ee.fullname" . }}-adc +subjects: + - kind: ServiceAccount + name: {{ include "api7ee.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} \ No newline at end of file diff --git a/helm/api7ee/templates/secret-api7.yaml b/helm/api7ee/templates/secret-api7.yaml new file mode 100644 index 0000000..11ea004 --- /dev/null +++ b/helm/api7ee/templates/secret-api7.yaml @@ -0,0 +1,14 @@ +{{- if .Values.api7.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "api7ee.fullname" . }}-api7-admin + labels: + {{- include "api7ee.labels" . | nindent 4 }} + app.kubernetes.io/component: api7 +type: Opaque +stringData: + admin-key: {{ .Values.api7.gateway.adminKey | quote }} + admin-url: {{ .Values.api7.gateway.adminUrl | quote }} + gateway-group: {{ .Values.api7.gateway.group | default "default" | quote }} +{{- end }} \ No newline at end of file diff --git a/helm/api7ee/values.yaml b/helm/api7ee/values.yaml index 0708165..75ef92c 100644 --- a/helm/api7ee/values.yaml +++ b/helm/api7ee/values.yaml @@ -194,4 +194,92 @@ configMap: # Secrets for sensitive data secrets: create: false - data: {} \ No newline at end of file + data: {} + +# API7 Gateway Configuration +api7: + enabled: true # Enable API7 ADC configuration + + # ADC Container settings + adc: + image: ghcr.io/api7/adc:latest + imagePullPolicy: IfNotPresent + verbose: true + tlsSkipVerify: false # Set to true for self-signed certificates + + # API7 Gateway connection + gateway: + adminUrl: http://api7-gateway.api7ee.svc.cluster.local:9180 + adminKey: "edd1c9f034335f136f87ad84b625c8f1" # Change this! + group: default + + # Backend type (api7ee or apisix) + backend: api7ee + + # Auto-publish routes after sync + autoPublish: true + + # Hosts for routing + hosts: + - demo.commandware.it + + # TLS/SSL Configuration + tls: + enabled: true + # Option 1: Use cert-manager + certManager: + enabled: true + issuer: letsencrypt-prod # ClusterIssuer name + issuerKind: ClusterIssuer # or Issuer + # Option 2: Use existing secret + secretName: "" # Name of existing TLS secret + # Option 3: Provide certificates directly (not recommended for production) + certificate: "" + key: "" + + # Service Discovery + serviceDiscovery: + enabled: true # Use Kubernetes service discovery + namespace: "" # Leave empty to use release namespace + + # API7 Plugins Configuration + plugins: + # Rate limiting + rateLimit: + enabled: true + count: 100 + timeWindow: 60 + apiCount: 1000 # Higher limit for API endpoints + + # CORS configuration + cors: + enabled: true + allowOrigins: ["*"] + allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"] + allowHeaders: ["*"] + exposeHeaders: ["*"] + maxAge: 3600 + allowCredentials: false + + # Authentication + auth: + enabled: false + header: X-API-Key + + # Prometheus metrics + prometheus: + enabled: true + + # Request logging + logging: + enabled: false + endpoint: http://logging-service:8080/logs + batchMaxSize: 1000 + inactiveTimeout: 5 + + # API Consumers (for authentication) + consumers: + - username: demo-user + apiKey: demo-key-12345 + - username: admin + apiKey: admin-key-67890 \ No newline at end of file