feat: add nginx-to-higress-migration skill (#3411)

This commit is contained in:
澄潭
2026-01-31 00:14:49 +08:00
committed by GitHub
parent 6c3fd46c6f
commit 4c2e57dd8b
13 changed files with 1994 additions and 14 deletions

View File

@@ -0,0 +1,304 @@
---
name: nginx-to-higress-migration
description: "Migrate from ingress-nginx to Higress in Kubernetes environments. Use when (1) analyzing existing ingress-nginx setup (2) reading nginx Ingress resources and ConfigMaps (3) installing Higress via helm with proper ingressClass (4) identifying unsupported nginx annotations (5) generating WASM plugins for nginx snippets/advanced features (6) building and deploying custom plugins to image registry. Supports full migration workflow with compatibility analysis and plugin generation."
---
# Nginx to Higress Migration
Automate migration from ingress-nginx to Higress in Kubernetes environments.
## Prerequisites
- kubectl configured with cluster access
- helm 3.x installed
- Go 1.24+ (for WASM plugin compilation)
- Docker (for plugin image push)
## Migration Workflow
### Phase 1: Discovery
```bash
# Check for ingress-nginx installation
kubectl get pods -A | grep ingress-nginx
kubectl get ingressclass
# List all Ingress resources using nginx class
kubectl get ingress -A -o json | jq '.items[] | select(.spec.ingressClassName=="nginx" or .metadata.annotations["kubernetes.io/ingress.class"]=="nginx")'
# Get nginx ConfigMap
kubectl get configmap -n ingress-nginx ingress-nginx-controller -o yaml
```
### Phase 2: Compatibility Analysis
Run the analysis script to identify unsupported features:
```bash
./scripts/analyze-ingress.sh [namespace]
```
**Key point: No Ingress modification needed!**
Higress natively supports `nginx.ingress.kubernetes.io/*` annotations - your existing Ingress resources work as-is.
See [references/annotation-mapping.md](references/annotation-mapping.md) for the complete list of supported annotations.
**Unsupported annotations** (require built-in plugin or custom WASM plugin):
- `nginx.ingress.kubernetes.io/server-snippet`
- `nginx.ingress.kubernetes.io/configuration-snippet`
- `nginx.ingress.kubernetes.io/lua-resty-waf*`
- Complex Lua logic in snippets
For these, check [references/builtin-plugins.md](references/builtin-plugins.md) first - Higress may already have a plugin!
### Phase 3: Higress Installation (Parallel with nginx)
Higress natively supports `nginx.ingress.kubernetes.io/*` annotations. Install Higress **alongside** nginx for safe parallel testing.
```bash
# 1. Get current nginx ingressClass name
INGRESS_CLASS=$(kubectl get ingressclass -o jsonpath='{.items[?(@.spec.controller=="k8s.io/ingress-nginx")].metadata.name}')
echo "Current nginx ingressClass: $INGRESS_CLASS"
# 2. Detect timezone and select nearest registry
# China/Asia: higress-registry.cn-hangzhou.cr.aliyuncs.com (default)
# North America: higress-registry.us-west-1.cr.aliyuncs.com
# Southeast Asia: higress-registry.ap-southeast-7.cr.aliyuncs.com
TZ_OFFSET=$(date +%z)
case "$TZ_OFFSET" in
-1*|-0*) REGISTRY="higress-registry.us-west-1.cr.aliyuncs.com" ;; # Americas
+07*|+08*|+09*) REGISTRY="higress-registry.cn-hangzhou.cr.aliyuncs.com" ;; # Asia
+05*|+06*) REGISTRY="higress-registry.ap-southeast-7.cr.aliyuncs.com" ;; # Southeast Asia
*) REGISTRY="higress-registry.cn-hangzhou.cr.aliyuncs.com" ;; # Default
esac
echo "Using registry: $REGISTRY"
# 3. Add Higress repo
helm repo add higress https://higress.io/helm-charts
helm repo update
# 4. Install Higress with parallel-safe settings
# Note: Override ALL component hubs to use the selected registry
helm install higress higress/higress \
-n higress-system --create-namespace \
--set global.ingressClass=${INGRESS_CLASS:-nginx} \
--set global.hub=${REGISTRY}/higress \
--set global.enableStatus=false \
--set higress-core.controller.hub=${REGISTRY}/higress \
--set higress-core.gateway.hub=${REGISTRY}/higress \
--set higress-core.pilot.hub=${REGISTRY}/higress \
--set higress-core.pluginServer.hub=${REGISTRY}/higress \
--set higress-core.gateway.replicas=2
```
Key helm values:
- `global.ingressClass`: Use the **same** class as ingress-nginx
- `global.hub`: Image registry (auto-selected by timezone)
- `global.enableStatus=false`: **Disable Ingress status updates** to avoid conflicts with nginx (reduces API server pressure)
- Override all component hubs to ensure consistent registry usage
- Both nginx and Higress will watch the same Ingress resources
- Higress automatically recognizes `nginx.ingress.kubernetes.io/*` annotations
- Traffic still flows through nginx until you switch the entry point
⚠️ **Note**: After nginx is uninstalled, you can enable status updates:
```bash
helm upgrade higress higress/higress -n higress-system \
--reuse-values \
--set global.enableStatus=true
```
### Phase 4: Generate and Run Test Script
After Higress is running, generate a test script covering all Ingress routes:
```bash
# Generate test script
./scripts/generate-migration-test.sh > migration-test.sh
chmod +x migration-test.sh
# Get Higress gateway address
# Option A: If LoadBalancer is supported
HIGRESS_IP=$(kubectl get svc -n higress-system higress-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# Option B: If LoadBalancer is NOT supported, use port-forward
kubectl port-forward -n higress-system svc/higress-gateway 8080:80 &
HIGRESS_IP="127.0.0.1:8080"
# Run tests
./migration-test.sh ${HIGRESS_IP}
```
The test script will:
- Extract all hosts and paths from Ingress resources
- Test each route against Higress gateway
- Verify response codes and basic functionality
- Report any failures for investigation
### Phase 5: Traffic Cutover (User Action Required)
⚠️ **Only proceed after all tests pass!**
Choose your cutover method based on infrastructure:
**Option A: DNS Switch**
```bash
# Update DNS records to point to Higress gateway IP
# Example: example.com A record -> ${HIGRESS_IP}
```
**Option B: Layer 4 Proxy/Load Balancer Switch**
```bash
# Update upstream in your L4 proxy (e.g., F5, HAProxy, cloud LB)
# From: nginx-ingress-controller service IP
# To: higress-gateway service IP
```
**Option C: Kubernetes Service Switch** (if using external traffic via Service)
```bash
# Update your external-facing Service selector or endpoints
```
### Phase 6: Use Built-in Plugins or Create Custom WASM Plugin (If Needed)
Before writing custom plugins, check if Higress has a built-in plugin that meets your needs!
#### Built-in Plugins (Recommended First)
Higress provides many built-in plugins. Check [references/builtin-plugins.md](references/builtin-plugins.md) for the full list.
Common replacements for nginx features:
| nginx feature | Higress built-in plugin |
|---------------|------------------------|
| Basic Auth snippet | `basic-auth` |
| IP restriction | `ip-restriction` |
| Rate limiting | `key-rate-limit`, `cluster-key-rate-limit` |
| WAF/ModSecurity | `waf` |
| Request validation | `request-validation` |
| Bot detection | `bot-detect` |
| JWT auth | `jwt-auth` |
| CORS headers | `cors` |
| Custom response | `custom-response` |
| Request/Response transform | `transformer` |
#### Custom WASM Plugin (If No Built-in Matches)
When nginx snippets or Lua logic has no built-in equivalent:
1. **Analyze snippet** - Extract nginx directives/Lua code
2. **Generate Go WASM code** - Use higress-wasm-go-plugin skill
3. **Build plugin**:
```bash
cd plugin-dir
go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./
```
4. **Push to registry**:
If you don't have an image registry, install Harbor:
```bash
./scripts/install-harbor.sh
# Follow the prompts to install Harbor in your cluster
```
If you have your own registry:
```bash
# Build OCI image
docker build -t <registry>/higress-plugin-<name>:v1 .
docker push <registry>/higress-plugin-<name>:v1
```
5. **Deploy plugin**:
```yaml
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: custom-plugin
namespace: higress-system
spec:
url: oci://<registry>/higress-plugin-<name>:v1
phase: UNSPECIFIED_PHASE
priority: 100
```
See [references/plugin-deployment.md](references/plugin-deployment.md) for detailed plugin deployment.
## Common Snippet Conversions
### Header Manipulation
```nginx
# nginx snippet
more_set_headers "X-Custom: value";
```
→ Use `headerControl` annotation or generate plugin with `proxywasm.AddHttpResponseHeader()`.
### Request Validation
```nginx
# nginx snippet
if ($request_uri ~* "pattern") { return 403; }
```
→ Generate WASM plugin with request header/path check.
### Rate Limiting with Custom Logic
```nginx
# nginx snippet with Lua
access_by_lua_block { ... }
```
→ Generate WASM plugin implementing the logic.
See [references/snippet-patterns.md](references/snippet-patterns.md) for common patterns.
## Validation
Before traffic switch, use the generated test script:
```bash
# Generate test script
./scripts/generate-migration-test.sh > migration-test.sh
chmod +x migration-test.sh
# Get Higress gateway IP
HIGRESS_IP=$(kubectl get svc -n higress-system higress-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# Run all tests
./migration-test.sh ${HIGRESS_IP}
```
The test script will:
- Test every host/path combination from all Ingress resources
- Report pass/fail for each route
- Provide a summary and next steps
**Only proceed with traffic cutover after all tests pass!**
## Rollback
Since nginx keeps running during migration, rollback is simply switching traffic back:
```bash
# If traffic was switched via DNS:
# - Revert DNS records to nginx gateway IP
# If traffic was switched via L4 proxy:
# - Revert upstream to nginx service IP
# Nginx is still running, no action needed on k8s side
```
## Post-Migration Cleanup
**Only after traffic has been fully migrated and stable:**
```bash
# 1. Monitor Higress for a period (recommended: 24-48h)
# 2. Backup nginx resources
kubectl get all -n ingress-nginx -o yaml > ingress-nginx-backup.yaml
# 3. Scale down nginx (keep for emergency rollback)
kubectl scale deployment -n ingress-nginx ingress-nginx-controller --replicas=0
# 4. (Optional) After extended stable period, remove nginx
kubectl delete namespace ingress-nginx
```

View File

@@ -0,0 +1,158 @@
# Nginx to Higress Annotation Compatibility
## ⚠️ Important: Do NOT Modify Your Ingress Resources!
**Higress natively supports `nginx.ingress.kubernetes.io/*` annotations** - no conversion or modification needed!
The Higress controller uses `ParseStringASAP()` which first tries `nginx.ingress.kubernetes.io/*` prefix, then falls back to `higress.io/*`. Your existing Ingress resources work as-is with Higress.
## Fully Compatible Annotations (Work As-Is)
These nginx annotations work directly with Higress without any changes:
| nginx annotation (keep as-is) | Higress also accepts | Notes |
|-------------------------------|---------------------|-------|
| `nginx.ingress.kubernetes.io/rewrite-target` | `higress.io/rewrite-target` | Supports capture groups |
| `nginx.ingress.kubernetes.io/use-regex` | `higress.io/use-regex` | Enable regex path matching |
| `nginx.ingress.kubernetes.io/ssl-redirect` | `higress.io/ssl-redirect` | Force HTTPS |
| `nginx.ingress.kubernetes.io/force-ssl-redirect` | `higress.io/force-ssl-redirect` | Same behavior |
| `nginx.ingress.kubernetes.io/backend-protocol` | `higress.io/backend-protocol` | HTTP/HTTPS/GRPC |
| `nginx.ingress.kubernetes.io/proxy-body-size` | `higress.io/proxy-body-size` | Max body size |
### CORS
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/enable-cors` | `higress.io/enable-cors` |
| `nginx.ingress.kubernetes.io/cors-allow-origin` | `higress.io/cors-allow-origin` |
| `nginx.ingress.kubernetes.io/cors-allow-methods` | `higress.io/cors-allow-methods` |
| `nginx.ingress.kubernetes.io/cors-allow-headers` | `higress.io/cors-allow-headers` |
| `nginx.ingress.kubernetes.io/cors-expose-headers` | `higress.io/cors-expose-headers` |
| `nginx.ingress.kubernetes.io/cors-allow-credentials` | `higress.io/cors-allow-credentials` |
| `nginx.ingress.kubernetes.io/cors-max-age` | `higress.io/cors-max-age` |
### Timeout & Retry
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/proxy-connect-timeout` | `higress.io/proxy-connect-timeout` |
| `nginx.ingress.kubernetes.io/proxy-send-timeout` | `higress.io/proxy-send-timeout` |
| `nginx.ingress.kubernetes.io/proxy-read-timeout` | `higress.io/proxy-read-timeout` |
| `nginx.ingress.kubernetes.io/proxy-next-upstream-tries` | `higress.io/proxy-next-upstream-tries` |
### Canary (Grayscale)
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/canary` | `higress.io/canary` |
| `nginx.ingress.kubernetes.io/canary-weight` | `higress.io/canary-weight` |
| `nginx.ingress.kubernetes.io/canary-header` | `higress.io/canary-header` |
| `nginx.ingress.kubernetes.io/canary-header-value` | `higress.io/canary-header-value` |
| `nginx.ingress.kubernetes.io/canary-header-pattern` | `higress.io/canary-header-pattern` |
| `nginx.ingress.kubernetes.io/canary-by-cookie` | `higress.io/canary-by-cookie` |
### Authentication
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/auth-type` | `higress.io/auth-type` |
| `nginx.ingress.kubernetes.io/auth-secret` | `higress.io/auth-secret` |
| `nginx.ingress.kubernetes.io/auth-realm` | `higress.io/auth-realm` |
### Load Balancing
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/load-balance` | `higress.io/load-balance` |
| `nginx.ingress.kubernetes.io/upstream-hash-by` | `higress.io/upstream-hash-by` |
### IP Access Control
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/whitelist-source-range` | `higress.io/whitelist-source-range` |
| `nginx.ingress.kubernetes.io/denylist-source-range` | `higress.io/denylist-source-range` |
### Redirect
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/permanent-redirect` | `higress.io/permanent-redirect` |
| `nginx.ingress.kubernetes.io/temporal-redirect` | `higress.io/temporal-redirect` |
| `nginx.ingress.kubernetes.io/permanent-redirect-code` | `higress.io/permanent-redirect-code` |
### Header Control
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/proxy-set-headers` | `higress.io/proxy-set-headers` |
| `nginx.ingress.kubernetes.io/proxy-hide-headers` | `higress.io/proxy-hide-headers` |
| `nginx.ingress.kubernetes.io/proxy-pass-headers` | `higress.io/proxy-pass-headers` |
### Upstream TLS
| nginx annotation | Higress annotation |
|------------------|-------------------|
| `nginx.ingress.kubernetes.io/proxy-ssl-secret` | `higress.io/proxy-ssl-secret` |
| `nginx.ingress.kubernetes.io/proxy-ssl-verify` | `higress.io/proxy-ssl-verify` |
## Unsupported Annotations (Require WASM Plugin)
These annotations have no direct Higress equivalent and require custom WASM plugins:
### Configuration Snippets
```yaml
# NOT supported - requires WASM plugin
nginx.ingress.kubernetes.io/server-snippet: |
location /custom { ... }
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Custom: value";
nginx.ingress.kubernetes.io/stream-snippet: |
# TCP/UDP snippets
```
### Lua Scripting
```yaml
# NOT supported - convert to WASM plugin
nginx.ingress.kubernetes.io/lua-resty-waf: "active"
nginx.ingress.kubernetes.io/lua-resty-waf-score-threshold: "10"
```
### ModSecurity
```yaml
# NOT supported - use Higress WAF plugin or custom WASM
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRule ...
```
### Rate Limiting (Complex)
```yaml
# Basic rate limiting supported via plugin
# Complex Lua-based rate limiting requires WASM
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-connections: "5"
```
### Other Unsupported
```yaml
# NOT directly supported
nginx.ingress.kubernetes.io/client-body-buffer-size
nginx.ingress.kubernetes.io/proxy-buffering
nginx.ingress.kubernetes.io/proxy-buffers-number
nginx.ingress.kubernetes.io/proxy-buffer-size
nginx.ingress.kubernetes.io/mirror-uri
nginx.ingress.kubernetes.io/mirror-request-body
nginx.ingress.kubernetes.io/grpc-backend
nginx.ingress.kubernetes.io/custom-http-errors
nginx.ingress.kubernetes.io/default-backend
```
## Migration Script
Use this script to analyze Ingress annotations:
```bash
# scripts/analyze-ingress.sh in this skill
./scripts/analyze-ingress.sh <namespace>
```

View File

@@ -0,0 +1,115 @@
# Higress Built-in Plugins
Before writing custom WASM plugins, check if Higress has a built-in plugin that meets your needs.
**Plugin docs and images**: https://github.com/higress-group/higress-console/tree/main/backend/sdk/src/main/resources/plugins
## Authentication & Authorization
| Plugin | Description | Replaces nginx feature |
|--------|-------------|----------------------|
| `basic-auth` | HTTP Basic Authentication | `auth_basic` directive |
| `jwt-auth` | JWT token validation | JWT Lua scripts |
| `key-auth` | API Key authentication | Custom auth headers |
| `hmac-auth` | HMAC signature authentication | Signature validation |
| `oauth` | OAuth 2.0 authentication | OAuth Lua scripts |
| `oidc` | OpenID Connect | OIDC integration |
| `ext-auth` | External authorization service | `auth_request` directive |
| `opa` | Open Policy Agent integration | Complex auth logic |
## Traffic Control
| Plugin | Description | Replaces nginx feature |
|--------|-------------|----------------------|
| `key-rate-limit` | Rate limiting by key | `limit_req` directive |
| `cluster-key-rate-limit` | Distributed rate limiting | `limit_req` with shared state |
| `ip-restriction` | IP whitelist/blacklist | `allow`/`deny` directives |
| `request-block` | Block requests by pattern | `if` + `return 403` |
| `traffic-tag` | Traffic tagging | Custom headers for routing |
| `bot-detect` | Bot detection & blocking | Bot detection Lua scripts |
## Request/Response Modification
| Plugin | Description | Replaces nginx feature |
|--------|-------------|----------------------|
| `transformer` | Transform request/response | `proxy_set_header`, `more_set_headers` |
| `cors` | CORS headers | `add_header` CORS headers |
| `custom-response` | Custom static response | `return` directive |
| `request-validation` | Request parameter validation | Validation Lua scripts |
| `de-graphql` | GraphQL to REST conversion | GraphQL handling |
## Security
| Plugin | Description | Replaces nginx feature |
|--------|-------------|----------------------|
| `waf` | Web Application Firewall | ModSecurity module |
| `geo-ip` | GeoIP-based access control | `geoip` module |
## Caching & Performance
| Plugin | Description | Replaces nginx feature |
|--------|-------------|----------------------|
| `cache-control` | Cache control headers | `expires`, `add_header Cache-Control` |
## AI Features (Higress-specific)
| Plugin | Description |
|--------|-------------|
| `ai-proxy` | AI model proxy |
| `ai-cache` | AI response caching |
| `ai-quota` | AI token quota |
| `ai-token-ratelimit` | AI token rate limiting |
| `ai-transformer` | AI request/response transform |
| `ai-security-guard` | AI content security |
| `ai-statistics` | AI usage statistics |
| `mcp-server` | Model Context Protocol server |
## Using Built-in Plugins
### Via WasmPlugin CRD
```yaml
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: basic-auth-plugin
namespace: higress-system
spec:
# Use built-in plugin image
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/basic-auth:1.0.0
phase: AUTHN
priority: 320
defaultConfig:
consumers:
- name: user1
credential: "admin:123456"
```
### Via Higress Console
1. Navigate to **Plugins****Plugin Market**
2. Find the desired plugin
3. Click **Enable** and configure
## Image Registry Locations
Select the nearest registry based on your location:
| Region | Registry |
|--------|----------|
| China/Default | `higress-registry.cn-hangzhou.cr.aliyuncs.com` |
| North America | `higress-registry.us-west-1.cr.aliyuncs.com` |
| Southeast Asia | `higress-registry.ap-southeast-7.cr.aliyuncs.com` |
Example with regional registry:
```yaml
spec:
url: oci://higress-registry.us-west-1.cr.aliyuncs.com/plugins/basic-auth:1.0.0
```
## Plugin Configuration Reference
Each plugin has its own configuration schema. View the spec.yaml in the plugin directory:
https://github.com/higress-group/higress-console/tree/main/backend/sdk/src/main/resources/plugins/<plugin-name>/spec.yaml
Or check the README files for detailed documentation.

View File

@@ -0,0 +1,245 @@
# WASM Plugin Build and Deployment
## Plugin Project Structure
```
my-plugin/
├── main.go # Plugin entry point
├── go.mod # Go module
├── go.sum # Dependencies
├── Dockerfile # OCI image build
└── wasmplugin.yaml # K8s deployment manifest
```
## Build Process
### 1. Initialize Project
```bash
mkdir my-plugin && cd my-plugin
go mod init my-plugin
# Set proxy (only needed in China due to network restrictions)
# Skip this step if you're outside China or have direct access to GitHub
go env -w GOPROXY=https://proxy.golang.com.cn,direct
# Get dependencies
go get github.com/higress-group/proxy-wasm-go-sdk@go-1.24
go get github.com/higress-group/wasm-go@main
go get github.com/tidwall/gjson
```
### 2. Write Plugin Code
See the higress-wasm-go-plugin skill for detailed API reference. Basic template:
```go
package main
import (
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {}
func init() {
wrapper.SetCtx(
"my-plugin",
wrapper.ParseConfig(parseConfig),
wrapper.ProcessRequestHeaders(onHttpRequestHeaders),
)
}
type MyConfig struct {
// Config fields
}
func parseConfig(json gjson.Result, config *MyConfig) error {
// Parse YAML config (converted to JSON)
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
// Process request
return types.HeaderContinue
}
```
### 3. Compile to WASM
```bash
go mod tidy
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./
```
### 4. Create Dockerfile
```dockerfile
FROM scratch
COPY main.wasm /plugin.wasm
```
### 5. Build and Push Image
#### Option A: Use Your Own Registry
```bash
# User provides registry
REGISTRY=your-registry.com/higress-plugins
# Build
docker build -t ${REGISTRY}/my-plugin:v1 .
# Push
docker push ${REGISTRY}/my-plugin:v1
```
#### Option B: Install Harbor (If No Registry Available)
If you don't have an image registry, we can install Harbor for you:
```bash
# Prerequisites
# - Kubernetes cluster with LoadBalancer or Ingress support
# - Persistent storage (PVC)
# - At least 4GB RAM and 2 CPU cores available
# Install Harbor via Helm
helm repo add harbor https://helm.goharbor.io
helm repo update
# Install with minimal configuration
helm install harbor harbor/harbor \
--namespace harbor-system --create-namespace \
--set expose.type=nodePort \
--set expose.tls.enabled=false \
--set persistence.enabled=true \
--set harborAdminPassword=Harbor12345
# Get Harbor access info
export NODE_PORT=$(kubectl get svc -n harbor-system harbor-core -o jsonpath='{.spec.ports[0].nodePort}')
export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}')
echo "Harbor URL: http://${NODE_IP}:${NODE_PORT}"
echo "Username: admin"
echo "Password: Harbor12345"
# Login to Harbor
docker login ${NODE_IP}:${NODE_PORT} -u admin -p Harbor12345
# Create project in Harbor UI (http://${NODE_IP}:${NODE_PORT})
# - Project Name: higress-plugins
# - Access Level: Public
# Build and push plugin
docker build -t ${NODE_IP}:${NODE_PORT}/higress-plugins/my-plugin:v1 .
docker push ${NODE_IP}:${NODE_PORT}/higress-plugins/my-plugin:v1
```
**Note**: For production use, enable TLS and use proper persistent storage.
## Deployment
### WasmPlugin CRD
```yaml
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: my-plugin
namespace: higress-system
spec:
# OCI image URL
url: oci://your-registry.com/higress-plugins/my-plugin:v1
# Plugin phase (when to execute)
# UNSPECIFIED_PHASE | AUTHN | AUTHZ | STATS
phase: UNSPECIFIED_PHASE
# Priority (higher = earlier execution)
priority: 100
# Plugin configuration
defaultConfig:
key: value
# Optional: specific routes/domains
matchRules:
- domain:
- "*.example.com"
config:
key: domain-specific-value
- ingress:
- default/my-ingress
config:
key: ingress-specific-value
```
### Apply to Cluster
```bash
kubectl apply -f wasmplugin.yaml
```
### Verify Deployment
```bash
# Check plugin status
kubectl get wasmplugin -n higress-system
# Check gateway logs
kubectl logs -n higress-system -l app=higress-gateway | grep -i plugin
# Test endpoint
curl -v http://<gateway-ip>/test-path
```
## Troubleshooting
### Plugin Not Loading
```bash
# Check image accessibility
kubectl run test --rm -it --image=your-registry.com/higress-plugins/my-plugin:v1 -- ls
# Check gateway events
kubectl describe pod -n higress-system -l app=higress-gateway
```
### Plugin Errors
```bash
# Enable debug logging
kubectl set env deployment/higress-gateway -n higress-system LOG_LEVEL=debug
# View plugin logs
kubectl logs -n higress-system -l app=higress-gateway -f
```
### Image Pull Issues
```bash
# Create image pull secret if needed
kubectl create secret docker-registry regcred \
--docker-server=your-registry.com \
--docker-username=user \
--docker-password=pass \
-n higress-system
# Reference in WasmPlugin
spec:
imagePullSecrets:
- name: regcred
```
## Plugin Configuration via Console
If using Higress Console:
1. Navigate to **Plugins****Custom Plugins**
2. Click **Add Plugin**
3. Enter OCI URL: `oci://your-registry.com/higress-plugins/my-plugin:v1`
4. Configure plugin settings
5. Apply to routes/domains as needed

View File

@@ -0,0 +1,331 @@
# Common Nginx Snippet to WASM Plugin Patterns
## Header Manipulation
### Add Response Header
**Nginx snippet:**
```nginx
more_set_headers "X-Custom-Header: custom-value";
more_set_headers "X-Request-ID: $request_id";
```
**WASM plugin:**
```go
func onHttpResponseHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
proxywasm.AddHttpResponseHeader("X-Custom-Header", "custom-value")
// For request ID, get from request context
if reqId, err := proxywasm.GetHttpRequestHeader("x-request-id"); err == nil {
proxywasm.AddHttpResponseHeader("X-Request-ID", reqId)
}
return types.HeaderContinue
}
```
### Remove Headers
**Nginx snippet:**
```nginx
more_clear_headers "Server";
more_clear_headers "X-Powered-By";
```
**WASM plugin:**
```go
func onHttpResponseHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
proxywasm.RemoveHttpResponseHeader("Server")
proxywasm.RemoveHttpResponseHeader("X-Powered-By")
return types.HeaderContinue
}
```
### Conditional Header
**Nginx snippet:**
```nginx
if ($http_x_custom_flag = "enabled") {
more_set_headers "X-Feature: active";
}
```
**WASM plugin:**
```go
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
flag, _ := proxywasm.GetHttpRequestHeader("x-custom-flag")
if flag == "enabled" {
proxywasm.AddHttpRequestHeader("X-Feature", "active")
}
return types.HeaderContinue
}
```
## Request Validation
### Block by Path Pattern
**Nginx snippet:**
```nginx
if ($request_uri ~* "(\.php|\.asp|\.aspx)$") {
return 403;
}
```
**WASM plugin:**
```go
import "regexp"
type MyConfig struct {
BlockPattern *regexp.Regexp
}
func parseConfig(json gjson.Result, config *MyConfig) error {
pattern := json.Get("blockPattern").String()
if pattern == "" {
pattern = `\.(php|asp|aspx)$`
}
config.BlockPattern = regexp.MustCompile(pattern)
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
path := ctx.Path()
if config.BlockPattern.MatchString(path) {
proxywasm.SendHttpResponse(403, nil, []byte("Forbidden"), -1)
return types.HeaderStopAllIterationAndWatermark
}
return types.HeaderContinue
}
```
### Block by User Agent
**Nginx snippet:**
```nginx
if ($http_user_agent ~* "(bot|crawler|spider)") {
return 403;
}
```
**WASM plugin:**
```go
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
ua, _ := proxywasm.GetHttpRequestHeader("user-agent")
ua = strings.ToLower(ua)
blockedPatterns := []string{"bot", "crawler", "spider"}
for _, pattern := range blockedPatterns {
if strings.Contains(ua, pattern) {
proxywasm.SendHttpResponse(403, nil, []byte("Blocked"), -1)
return types.HeaderStopAllIterationAndWatermark
}
}
return types.HeaderContinue
}
```
### Request Size Validation
**Nginx snippet:**
```nginx
if ($content_length > 10485760) {
return 413;
}
```
**WASM plugin:**
```go
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
clStr, _ := proxywasm.GetHttpRequestHeader("content-length")
if cl, err := strconv.ParseInt(clStr, 10, 64); err == nil {
if cl > 10*1024*1024 { // 10MB
proxywasm.SendHttpResponse(413, nil, []byte("Request too large"), -1)
return types.HeaderStopAllIterationAndWatermark
}
}
return types.HeaderContinue
}
```
## Request Modification
### URL Rewrite with Logic
**Nginx snippet:**
```nginx
set $backend "default";
if ($http_x_version = "v2") {
set $backend "v2";
}
rewrite ^/api/(.*)$ /api/$backend/$1 break;
```
**WASM plugin:**
```go
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
version, _ := proxywasm.GetHttpRequestHeader("x-version")
backend := "default"
if version == "v2" {
backend = "v2"
}
path := ctx.Path()
if strings.HasPrefix(path, "/api/") {
newPath := "/api/" + backend + path[4:]
proxywasm.ReplaceHttpRequestHeader(":path", newPath)
}
return types.HeaderContinue
}
```
### Add Query Parameter
**Nginx snippet:**
```nginx
if ($args !~ "source=") {
set $args "${args}&source=gateway";
}
```
**WASM plugin:**
```go
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
path := ctx.Path()
if !strings.Contains(path, "source=") {
separator := "?"
if strings.Contains(path, "?") {
separator = "&"
}
newPath := path + separator + "source=gateway"
proxywasm.ReplaceHttpRequestHeader(":path", newPath)
}
return types.HeaderContinue
}
```
## Lua Script Conversion
### Simple Lua Access Check
**Nginx Lua:**
```lua
access_by_lua_block {
local token = ngx.var.http_authorization
if not token or token == "" then
ngx.exit(401)
end
}
```
**WASM plugin:**
```go
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
token, _ := proxywasm.GetHttpRequestHeader("authorization")
if token == "" {
proxywasm.SendHttpResponse(401, [][2]string{
{"WWW-Authenticate", "Bearer"},
}, []byte("Unauthorized"), -1)
return types.HeaderStopAllIterationAndWatermark
}
return types.HeaderContinue
}
```
### Lua with Redis
**Nginx Lua:**
```lua
access_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:connect("127.0.0.1", 6379)
local ip = ngx.var.remote_addr
local count = red:incr("rate:" .. ip)
if count > 100 then
ngx.exit(429)
end
red:expire("rate:" .. ip, 60)
}
```
**WASM plugin:**
```go
// See references/redis-client.md in higress-wasm-go-plugin skill
func parseConfig(json gjson.Result, config *MyConfig) error {
config.redis = wrapper.NewRedisClusterClient(wrapper.FQDNCluster{
FQDN: json.Get("redisService").String(),
Port: json.Get("redisPort").Int(),
})
return config.redis.Init("", json.Get("redisPassword").String(), 1000)
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
ip, _ := proxywasm.GetHttpRequestHeader("x-real-ip")
if ip == "" {
ip, _ = proxywasm.GetHttpRequestHeader("x-forwarded-for")
}
key := "rate:" + ip
err := config.redis.Incr(key, func(val int) {
if val > 100 {
proxywasm.SendHttpResponse(429, nil, []byte("Rate limited"), -1)
return
}
config.redis.Expire(key, 60, nil)
proxywasm.ResumeHttpRequest()
})
if err != nil {
return types.HeaderContinue // Fallback on Redis error
}
return types.HeaderStopAllIterationAndWatermark
}
```
## Response Modification
### Inject Script/Content
**Nginx snippet:**
```nginx
sub_filter '</head>' '<script src="/tracking.js"></script></head>';
sub_filter_once on;
```
**WASM plugin:**
```go
func init() {
wrapper.SetCtx(
"inject-script",
wrapper.ParseConfig(parseConfig),
wrapper.ProcessResponseHeaders(onHttpResponseHeaders),
wrapper.ProcessResponseBody(onHttpResponseBody),
)
}
func onHttpResponseHeaders(ctx wrapper.HttpContext, config MyConfig) types.Action {
contentType, _ := proxywasm.GetHttpResponseHeader("content-type")
if strings.Contains(contentType, "text/html") {
ctx.BufferResponseBody()
proxywasm.RemoveHttpResponseHeader("content-length")
}
return types.HeaderContinue
}
func onHttpResponseBody(ctx wrapper.HttpContext, config MyConfig, body []byte) types.Action {
bodyStr := string(body)
injection := `<script src="/tracking.js"></script></head>`
newBody := strings.Replace(bodyStr, "</head>", injection, 1)
proxywasm.ReplaceHttpResponseBody([]byte(newBody))
return types.BodyContinue
}
```
## Best Practices
1. **Error Handling**: Always handle external call failures gracefully
2. **Performance**: Cache regex patterns in config, avoid recompiling
3. **Timeout**: Set appropriate timeouts for external calls (default 500ms)
4. **Logging**: Use `proxywasm.LogInfo/Warn/Error` for debugging
5. **Testing**: Test locally with Docker Compose before deploying

View File

@@ -0,0 +1,198 @@
#!/bin/bash
# Analyze nginx Ingress resources and identify migration requirements
set -e
NAMESPACE="${1:-}"
OUTPUT_FORMAT="${2:-text}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Supported nginx annotations that map to Higress
SUPPORTED_ANNOTATIONS=(
"rewrite-target"
"use-regex"
"ssl-redirect"
"force-ssl-redirect"
"backend-protocol"
"proxy-body-size"
"enable-cors"
"cors-allow-origin"
"cors-allow-methods"
"cors-allow-headers"
"cors-expose-headers"
"cors-allow-credentials"
"cors-max-age"
"proxy-connect-timeout"
"proxy-send-timeout"
"proxy-read-timeout"
"proxy-next-upstream-tries"
"canary"
"canary-weight"
"canary-header"
"canary-header-value"
"canary-header-pattern"
"canary-by-cookie"
"auth-type"
"auth-secret"
"auth-realm"
"load-balance"
"upstream-hash-by"
"whitelist-source-range"
"denylist-source-range"
"permanent-redirect"
"temporal-redirect"
"permanent-redirect-code"
"proxy-set-headers"
"proxy-hide-headers"
"proxy-pass-headers"
"proxy-ssl-secret"
"proxy-ssl-verify"
)
# Unsupported annotations requiring WASM plugins
UNSUPPORTED_ANNOTATIONS=(
"server-snippet"
"configuration-snippet"
"stream-snippet"
"lua-resty-waf"
"lua-resty-waf-score-threshold"
"enable-modsecurity"
"modsecurity-snippet"
"limit-rps"
"limit-connections"
"limit-rate"
"limit-rate-after"
"client-body-buffer-size"
"proxy-buffering"
"proxy-buffers-number"
"proxy-buffer-size"
"custom-http-errors"
"default-backend"
)
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Nginx to Higress Migration Analysis${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
# Check for ingress-nginx
echo -e "${YELLOW}Checking for ingress-nginx...${NC}"
if kubectl get pods -A 2>/dev/null | grep -q ingress-nginx; then
echo -e "${GREEN}✓ ingress-nginx found${NC}"
kubectl get pods -A | grep ingress-nginx | head -5
else
echo -e "${RED}✗ ingress-nginx not found${NC}"
fi
echo ""
# Check IngressClass
echo -e "${YELLOW}IngressClass resources:${NC}"
kubectl get ingressclass 2>/dev/null || echo "No IngressClass resources found"
echo ""
# Get Ingress resources
if [ -n "$NAMESPACE" ]; then
INGRESS_LIST=$(kubectl get ingress -n "$NAMESPACE" -o json 2>/dev/null)
else
INGRESS_LIST=$(kubectl get ingress -A -o json 2>/dev/null)
fi
if [ -z "$INGRESS_LIST" ] || [ "$(echo "$INGRESS_LIST" | jq '.items | length')" -eq 0 ]; then
echo -e "${RED}No Ingress resources found${NC}"
exit 0
fi
TOTAL_INGRESS=$(echo "$INGRESS_LIST" | jq '.items | length')
echo -e "${YELLOW}Found ${TOTAL_INGRESS} Ingress resources${NC}"
echo ""
# Analyze each Ingress
COMPATIBLE_COUNT=0
NEEDS_PLUGIN_COUNT=0
UNSUPPORTED_FOUND=()
echo "$INGRESS_LIST" | jq -c '.items[]' | while read -r ingress; do
NAME=$(echo "$ingress" | jq -r '.metadata.name')
NS=$(echo "$ingress" | jq -r '.metadata.namespace')
INGRESS_CLASS=$(echo "$ingress" | jq -r '.spec.ingressClassName // .metadata.annotations["kubernetes.io/ingress.class"] // "unknown"')
# Skip non-nginx ingresses
if [[ "$INGRESS_CLASS" != "nginx" && "$INGRESS_CLASS" != "unknown" ]]; then
continue
fi
echo -e "${BLUE}-------------------------------------------${NC}"
echo -e "${BLUE}Ingress: ${NS}/${NAME}${NC}"
echo -e "IngressClass: ${INGRESS_CLASS}"
# Get annotations
ANNOTATIONS=$(echo "$ingress" | jq -r '.metadata.annotations // {}')
HAS_UNSUPPORTED=false
SUPPORTED_LIST=()
UNSUPPORTED_LIST=()
# Check each annotation
echo "$ANNOTATIONS" | jq -r 'keys[]' | while read -r key; do
# Extract annotation name (remove prefix)
ANNO_NAME=$(echo "$key" | sed 's/nginx.ingress.kubernetes.io\///' | sed 's/higress.io\///')
if [[ "$key" == nginx.ingress.kubernetes.io/* ]]; then
# Check if supported
IS_SUPPORTED=false
for supported in "${SUPPORTED_ANNOTATIONS[@]}"; do
if [[ "$ANNO_NAME" == "$supported" ]]; then
IS_SUPPORTED=true
break
fi
done
# Check if explicitly unsupported
for unsupported in "${UNSUPPORTED_ANNOTATIONS[@]}"; do
if [[ "$ANNO_NAME" == "$unsupported" ]]; then
IS_SUPPORTED=false
HAS_UNSUPPORTED=true
VALUE=$(echo "$ANNOTATIONS" | jq -r --arg k "$key" '.[$k]')
echo -e " ${RED}$ANNO_NAME${NC} (requires WASM plugin)"
if [[ "$ANNO_NAME" == *"snippet"* ]]; then
echo -e " Value preview: $(echo "$VALUE" | head -1)"
fi
break
fi
done
if [ "$IS_SUPPORTED" = true ]; then
echo -e " ${GREEN}$ANNO_NAME${NC}"
fi
fi
done
if [ "$HAS_UNSUPPORTED" = true ]; then
echo -e "\n ${YELLOW}Status: Requires WASM plugin for full compatibility${NC}"
else
echo -e "\n ${GREEN}Status: Fully compatible${NC}"
fi
echo ""
done
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Summary${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "Total Ingress resources: ${TOTAL_INGRESS}"
echo ""
echo -e "${GREEN}✓ No Ingress modification needed!${NC}"
echo " Higress natively supports nginx.ingress.kubernetes.io/* annotations."
echo ""
echo -e "${YELLOW}Next Steps:${NC}"
echo "1. Install Higress with the SAME ingressClass as nginx"
echo " (set global.enableStatus=false to disable Ingress status updates)"
echo "2. For snippets/Lua: check Higress built-in plugins first, then generate custom WASM if needed"
echo "3. Generate and run migration test script"
echo "4. Switch traffic via DNS or L4 proxy after tests pass"
echo "5. After stable period, uninstall nginx and enable status updates (global.enableStatus=true)"

View File

@@ -0,0 +1,210 @@
#!/bin/bash
# Generate test script for all Ingress routes
# Tests each route against Higress gateway to validate migration
set -e
NAMESPACE="${1:-}"
# Colors for output script
cat << 'HEADER'
#!/bin/bash
# Higress Migration Test Script
# Auto-generated - tests all Ingress routes against Higress gateway
set -e
GATEWAY_IP="${1:-}"
TIMEOUT="${2:-5}"
VERBOSE="${3:-false}"
if [ -z "$GATEWAY_IP" ]; then
echo "Usage: $0 <higress-gateway-ip[:port]> [timeout] [verbose]"
echo ""
echo "Examples:"
echo " # With LoadBalancer IP"
echo " $0 10.0.0.100 5 true"
echo ""
echo " # With port-forward (run this first: kubectl port-forward -n higress-system svc/higress-gateway 8080:80 &)"
echo " $0 127.0.0.1:8080 5 true"
exit 1
fi
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
TOTAL=0
PASSED=0
FAILED=0
FAILED_TESTS=()
test_route() {
local host="$1"
local path="$2"
local expected_code="${3:-200}"
local description="$4"
TOTAL=$((TOTAL + 1))
# Build URL
local url="http://${GATEWAY_IP}${path}"
# Make request
local response
response=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Host: ${host}" \
--connect-timeout "${TIMEOUT}" \
--max-time $((TIMEOUT * 2)) \
"${url}" 2>/dev/null) || response="000"
# Check result
if [ "$response" = "$expected_code" ] || [ "$expected_code" = "*" ]; then
PASSED=$((PASSED + 1))
echo -e "${GREEN}✓${NC} [${response}] ${host}${path}"
if [ "$VERBOSE" = "true" ]; then
echo " Expected: ${expected_code}, Got: ${response}"
fi
else
FAILED=$((FAILED + 1))
FAILED_TESTS+=("${host}${path} (expected ${expected_code}, got ${response})")
echo -e "${RED}✗${NC} [${response}] ${host}${path}"
echo " Expected: ${expected_code}, Got: ${response}"
fi
}
echo "========================================"
echo "Higress Migration Test"
echo "========================================"
echo "Gateway IP: ${GATEWAY_IP}"
echo "Timeout: ${TIMEOUT}s"
echo ""
echo "Testing routes..."
echo ""
HEADER
# Get Ingress resources
if [ -n "$NAMESPACE" ]; then
INGRESS_JSON=$(kubectl get ingress -n "$NAMESPACE" -o json 2>/dev/null)
else
INGRESS_JSON=$(kubectl get ingress -A -o json 2>/dev/null)
fi
if [ -z "$INGRESS_JSON" ] || [ "$(echo "$INGRESS_JSON" | jq '.items | length')" -eq 0 ]; then
echo "# No Ingress resources found"
echo "echo 'No Ingress resources found to test'"
echo "exit 0"
exit 0
fi
# Generate test cases for each Ingress
echo "$INGRESS_JSON" | jq -c '.items[]' | while read -r ingress; do
NAME=$(echo "$ingress" | jq -r '.metadata.name')
NS=$(echo "$ingress" | jq -r '.metadata.namespace')
echo ""
echo "# ================================================"
echo "# Ingress: ${NS}/${NAME}"
echo "# ================================================"
# Check for TLS hosts
TLS_HOSTS=$(echo "$ingress" | jq -r '.spec.tls[]?.hosts[]?' 2>/dev/null | sort -u)
# Process each rule
echo "$ingress" | jq -c '.spec.rules[]?' | while read -r rule; do
HOST=$(echo "$rule" | jq -r '.host // "*"')
# Process each path
echo "$rule" | jq -c '.http.paths[]?' | while read -r path_item; do
PATH=$(echo "$path_item" | jq -r '.path // "/"')
PATH_TYPE=$(echo "$path_item" | jq -r '.pathType // "Prefix"')
SERVICE=$(echo "$path_item" | jq -r '.backend.service.name // .backend.serviceName // "unknown"')
PORT=$(echo "$path_item" | jq -r '.backend.service.port.number // .backend.service.port.name // .backend.servicePort // "80"')
# Generate test
# For Prefix paths, test the exact path
# For Exact paths, test exactly
# Add a simple 200 or * expectation (can be customized)
echo ""
echo "# Path: ${PATH} (${PATH_TYPE}) -> ${SERVICE}:${PORT}"
# Test the path
if [ "$PATH_TYPE" = "Exact" ]; then
echo "test_route \"${HOST}\" \"${PATH}\" \"*\" \"Exact path\""
else
# For Prefix, test base path and a subpath
echo "test_route \"${HOST}\" \"${PATH}\" \"*\" \"Prefix path\""
# If path doesn't end with /, add a subpath test
if [[ ! "$PATH" =~ /$ ]] && [ "$PATH" != "/" ]; then
echo "test_route \"${HOST}\" \"${PATH}/\" \"*\" \"Prefix path with trailing slash\""
fi
fi
done
done
# Check for specific annotations that might need special testing
REWRITE=$(echo "$ingress" | jq -r '.metadata.annotations["nginx.ingress.kubernetes.io/rewrite-target"] // .metadata.annotations["higress.io/rewrite-target"] // ""')
if [ -n "$REWRITE" ] && [ "$REWRITE" != "null" ]; then
echo ""
echo "# Note: This Ingress has rewrite-target: ${REWRITE}"
echo "# Verify the rewritten path manually if needed"
fi
CANARY=$(echo "$ingress" | jq -r '.metadata.annotations["nginx.ingress.kubernetes.io/canary"] // .metadata.annotations["higress.io/canary"] // ""')
if [ "$CANARY" = "true" ]; then
echo ""
echo "# Note: This is a canary Ingress - test with appropriate headers/cookies"
CANARY_HEADER=$(echo "$ingress" | jq -r '.metadata.annotations["nginx.ingress.kubernetes.io/canary-header"] // .metadata.annotations["higress.io/canary-header"] // ""')
CANARY_VALUE=$(echo "$ingress" | jq -r '.metadata.annotations["nginx.ingress.kubernetes.io/canary-header-value"] // .metadata.annotations["higress.io/canary-header-value"] // ""')
if [ -n "$CANARY_HEADER" ] && [ "$CANARY_HEADER" != "null" ]; then
echo "# Canary header: ${CANARY_HEADER}=${CANARY_VALUE}"
fi
fi
done
# Generate summary section
cat << 'FOOTER'
# ================================================
# Summary
# ================================================
echo ""
echo "========================================"
echo "Test Summary"
echo "========================================"
echo -e "Total: ${TOTAL}"
echo -e "Passed: ${GREEN}${PASSED}${NC}"
echo -e "Failed: ${RED}${FAILED}${NC}"
echo ""
if [ ${FAILED} -gt 0 ]; then
echo -e "${YELLOW}Failed tests:${NC}"
for test in "${FAILED_TESTS[@]}"; do
echo -e " ${RED}•${NC} $test"
done
echo ""
echo -e "${YELLOW}⚠ Some tests failed. Please investigate before switching traffic.${NC}"
exit 1
else
echo -e "${GREEN}✓ All tests passed!${NC}"
echo ""
echo "========================================"
echo -e "${GREEN}Ready for Traffic Cutover${NC}"
echo "========================================"
echo ""
echo "Next steps:"
echo "1. Switch traffic to Higress gateway:"
echo " - DNS: Update A/CNAME records to ${GATEWAY_IP}"
echo " - L4 Proxy: Update upstream to ${GATEWAY_IP}"
echo ""
echo "2. Monitor for errors after switch"
echo ""
echo "3. Once stable, scale down nginx:"
echo " kubectl scale deployment -n ingress-nginx ingress-nginx-controller --replicas=0"
echo ""
fi
FOOTER

View File

@@ -0,0 +1,261 @@
#!/bin/bash
# Generate WASM plugin scaffold for nginx snippet migration
set -e
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <plugin-name> [output-dir]"
echo ""
echo "Example: $0 custom-headers ./plugins"
exit 1
fi
PLUGIN_NAME="$1"
OUTPUT_DIR="${2:-.}"
PLUGIN_DIR="${OUTPUT_DIR}/${PLUGIN_NAME}"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo -e "${YELLOW}Generating WASM plugin scaffold: ${PLUGIN_NAME}${NC}"
# Create directory
mkdir -p "$PLUGIN_DIR"
# Generate go.mod
cat > "${PLUGIN_DIR}/go.mod" << EOF
module ${PLUGIN_NAME}
go 1.24
require (
github.com/higress-group/proxy-wasm-go-sdk v1.0.1-0.20241230091623-edc7227eb588
github.com/higress-group/wasm-go v1.0.1-0.20250107151137-19a0ab53cfec
github.com/tidwall/gjson v1.18.0
)
EOF
# Generate main.go
cat > "${PLUGIN_DIR}/main.go" << 'EOF'
package main
import (
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/higress-group/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)
func main() {}
func init() {
wrapper.SetCtx(
"PLUGIN_NAME_PLACEHOLDER",
wrapper.ParseConfig(parseConfig),
wrapper.ProcessRequestHeaders(onHttpRequestHeaders),
wrapper.ProcessRequestBody(onHttpRequestBody),
wrapper.ProcessResponseHeaders(onHttpResponseHeaders),
wrapper.ProcessResponseBody(onHttpResponseBody),
)
}
// PluginConfig holds the plugin configuration
type PluginConfig struct {
// TODO: Add configuration fields
// Example:
// HeaderName string
// HeaderValue string
Enabled bool
}
// parseConfig parses the plugin configuration from YAML (converted to JSON)
func parseConfig(json gjson.Result, config *PluginConfig) error {
// TODO: Parse configuration
// Example:
// config.HeaderName = json.Get("headerName").String()
// config.HeaderValue = json.Get("headerValue").String()
config.Enabled = json.Get("enabled").Bool()
proxywasm.LogInfof("Plugin config loaded: enabled=%v", config.Enabled)
return nil
}
// onHttpRequestHeaders is called when request headers are received
func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig) types.Action {
if !config.Enabled {
return types.HeaderContinue
}
// TODO: Implement request header processing
// Example: Add custom header
// proxywasm.AddHttpRequestHeader(config.HeaderName, config.HeaderValue)
// Example: Check path and block
// path := ctx.Path()
// if strings.Contains(path, "/blocked") {
// proxywasm.SendHttpResponse(403, nil, []byte("Forbidden"), -1)
// return types.HeaderStopAllIterationAndWatermark
// }
return types.HeaderContinue
}
// onHttpRequestBody is called when request body is received
// Remove this function from init() if not needed
func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte) types.Action {
if !config.Enabled {
return types.BodyContinue
}
// TODO: Implement request body processing
// Example: Log body size
// proxywasm.LogInfof("Request body size: %d", len(body))
return types.BodyContinue
}
// onHttpResponseHeaders is called when response headers are received
func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig) types.Action {
if !config.Enabled {
return types.HeaderContinue
}
// TODO: Implement response header processing
// Example: Add security headers
// proxywasm.AddHttpResponseHeader("X-Content-Type-Options", "nosniff")
// proxywasm.AddHttpResponseHeader("X-Frame-Options", "DENY")
return types.HeaderContinue
}
// onHttpResponseBody is called when response body is received
// Remove this function from init() if not needed
func onHttpResponseBody(ctx wrapper.HttpContext, config PluginConfig, body []byte) types.Action {
if !config.Enabled {
return types.BodyContinue
}
// TODO: Implement response body processing
// Example: Modify response body
// newBody := strings.Replace(string(body), "old", "new", -1)
// proxywasm.ReplaceHttpResponseBody([]byte(newBody))
return types.BodyContinue
}
EOF
# Replace plugin name placeholder
sed -i "s/PLUGIN_NAME_PLACEHOLDER/${PLUGIN_NAME}/g" "${PLUGIN_DIR}/main.go"
# Generate Dockerfile
cat > "${PLUGIN_DIR}/Dockerfile" << 'EOF'
FROM scratch
COPY main.wasm /plugin.wasm
EOF
# Generate build script
cat > "${PLUGIN_DIR}/build.sh" << 'EOF'
#!/bin/bash
set -e
echo "Downloading dependencies..."
go mod tidy
echo "Building WASM plugin..."
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm ./
echo "Build complete: main.wasm"
ls -lh main.wasm
EOF
chmod +x "${PLUGIN_DIR}/build.sh"
# Generate WasmPlugin manifest
cat > "${PLUGIN_DIR}/wasmplugin.yaml" << EOF
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: ${PLUGIN_NAME}
namespace: higress-system
spec:
# TODO: Replace with your registry
url: oci://YOUR_REGISTRY/${PLUGIN_NAME}:v1
phase: UNSPECIFIED_PHASE
priority: 100
defaultConfig:
enabled: true
# TODO: Add your configuration
# Optional: Apply to specific routes/domains
# matchRules:
# - domain:
# - "*.example.com"
# config:
# enabled: true
EOF
# Generate README
cat > "${PLUGIN_DIR}/README.md" << EOF
# ${PLUGIN_NAME}
A Higress WASM plugin migrated from nginx configuration.
## Build
\`\`\`bash
./build.sh
\`\`\`
## Push to Registry
\`\`\`bash
# Set your registry
REGISTRY=your-registry.com/higress-plugins
# Build Docker image
docker build -t \${REGISTRY}/${PLUGIN_NAME}:v1 .
# Push
docker push \${REGISTRY}/${PLUGIN_NAME}:v1
\`\`\`
## Deploy
1. Update \`wasmplugin.yaml\` with your registry URL
2. Apply to cluster:
\`\`\`bash
kubectl apply -f wasmplugin.yaml
\`\`\`
## Configuration
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| enabled | bool | true | Enable/disable plugin |
## TODO
- [ ] Implement plugin logic in main.go
- [ ] Add configuration fields
- [ ] Test locally
- [ ] Push to registry
- [ ] Deploy to cluster
EOF
echo -e "\n${GREEN}✓ Plugin scaffold generated at: ${PLUGIN_DIR}${NC}"
echo ""
echo "Files created:"
echo " - ${PLUGIN_DIR}/main.go (plugin source)"
echo " - ${PLUGIN_DIR}/go.mod (Go module)"
echo " - ${PLUGIN_DIR}/Dockerfile (OCI image)"
echo " - ${PLUGIN_DIR}/build.sh (build script)"
echo " - ${PLUGIN_DIR}/wasmplugin.yaml (K8s manifest)"
echo " - ${PLUGIN_DIR}/README.md (documentation)"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. cd ${PLUGIN_DIR}"
echo "2. Edit main.go to implement your logic"
echo "3. Run: ./build.sh"
echo "4. Push image to your registry"
echo "5. Update wasmplugin.yaml with registry URL"
echo "6. Deploy: kubectl apply -f wasmplugin.yaml"

View File

@@ -0,0 +1,157 @@
#!/bin/bash
# Install Harbor registry for WASM plugin images
# Only use this if you don't have an existing image registry
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
HARBOR_NAMESPACE="${1:-harbor-system}"
HARBOR_PASSWORD="${2:-Harbor12345}"
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Harbor Registry Installation${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e "${YELLOW}This will install Harbor in your cluster.${NC}"
echo ""
echo "Configuration:"
echo " Namespace: ${HARBOR_NAMESPACE}"
echo " Admin Password: ${HARBOR_PASSWORD}"
echo " Exposure: NodePort (no TLS)"
echo " Persistence: Enabled (default StorageClass)"
echo ""
read -p "Continue? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 1
fi
# Check prerequisites
echo -e "\n${YELLOW}Checking prerequisites...${NC}"
# Check for helm
if ! command -v helm &> /dev/null; then
echo -e "${RED}✗ helm not found. Please install helm 3.x${NC}"
exit 1
fi
echo -e "${GREEN}✓ helm found${NC}"
# Check for kubectl
if ! command -v kubectl &> /dev/null; then
echo -e "${RED}✗ kubectl not found${NC}"
exit 1
fi
echo -e "${GREEN}✓ kubectl found${NC}"
# Check cluster access
if ! kubectl get nodes &> /dev/null; then
echo -e "${RED}✗ Cannot access cluster${NC}"
exit 1
fi
echo -e "${GREEN}✓ Cluster access OK${NC}"
# Check for default StorageClass
if ! kubectl get storageclass -o name | grep -q .; then
echo -e "${YELLOW}⚠ No StorageClass found. Harbor needs persistent storage.${NC}"
echo " You may need to install a storage provisioner first."
read -p "Continue anyway? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Add Harbor helm repo
echo -e "\n${YELLOW}Adding Harbor helm repository...${NC}"
helm repo add harbor https://helm.goharbor.io
helm repo update
echo -e "${GREEN}✓ Repository added${NC}"
# Install Harbor
echo -e "\n${YELLOW}Installing Harbor...${NC}"
helm install harbor harbor/harbor \
--namespace "${HARBOR_NAMESPACE}" --create-namespace \
--set expose.type=nodePort \
--set expose.tls.enabled=false \
--set persistence.enabled=true \
--set harborAdminPassword="${HARBOR_PASSWORD}" \
--wait --timeout 10m
if [ $? -ne 0 ]; then
echo -e "${RED}✗ Harbor installation failed${NC}"
exit 1
fi
echo -e "${GREEN}✓ Harbor installed successfully${NC}"
# Wait for Harbor to be ready
echo -e "\n${YELLOW}Waiting for Harbor to be ready...${NC}"
kubectl wait --for=condition=ready pod -l app=harbor -n "${HARBOR_NAMESPACE}" --timeout=300s
# Get access information
echo -e "\n${BLUE}========================================${NC}"
echo -e "${BLUE}Harbor Access Information${NC}"
echo -e "${BLUE}========================================${NC}"
NODE_PORT=$(kubectl get svc -n "${HARBOR_NAMESPACE}" harbor-core -o jsonpath='{.spec.ports[0].nodePort}')
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}')
if [ -z "$NODE_IP" ]; then
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
fi
HARBOR_URL="${NODE_IP}:${NODE_PORT}"
echo ""
echo -e "Harbor URL: ${GREEN}http://${HARBOR_URL}${NC}"
echo -e "Username: ${GREEN}admin${NC}"
echo -e "Password: ${GREEN}${HARBOR_PASSWORD}${NC}"
echo ""
# Test Docker login
echo -e "${YELLOW}Testing Docker login...${NC}"
if docker login "${HARBOR_URL}" -u admin -p "${HARBOR_PASSWORD}" &> /dev/null; then
echo -e "${GREEN}✓ Docker login successful${NC}"
else
echo -e "${YELLOW}⚠ Docker login failed. You may need to:${NC}"
echo " 1. Add '${HARBOR_URL}' to Docker's insecure registries"
echo " 2. Restart Docker daemon"
echo ""
echo " Edit /etc/docker/daemon.json (Linux) or Docker Desktop settings (Mac/Windows):"
echo " {"
echo " \"insecure-registries\": [\"${HARBOR_URL}\"]"
echo " }"
fi
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Next Steps${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo "1. Open Harbor UI: http://${HARBOR_URL}"
echo "2. Login with admin/${HARBOR_PASSWORD}"
echo "3. Create a new project:"
echo " - Click 'Projects' → 'New Project'"
echo " - Name: higress-plugins"
echo " - Access Level: Public"
echo ""
echo "4. Build and push your plugin:"
echo " docker build -t ${HARBOR_URL}/higress-plugins/my-plugin:v1 ."
echo " docker push ${HARBOR_URL}/higress-plugins/my-plugin:v1"
echo ""
echo "5. Use in WasmPlugin:"
echo " url: oci://${HARBOR_URL}/higress-plugins/my-plugin:v1"
echo ""
echo -e "${YELLOW}⚠ Note: This is a basic installation for testing.${NC}"
echo " For production use:"
echo " - Enable TLS (set expose.tls.enabled=true)"
echo " - Use LoadBalancer or Ingress instead of NodePort"
echo " - Configure proper persistent storage"
echo " - Set strong admin password"
echo ""