feat(temporal): install Temporal
This commit is contained in:
401
temporal/README.md
Normal file
401
temporal/README.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# Temporal
|
||||
|
||||
Durable workflow execution platform for building reliable distributed applications:
|
||||
|
||||
- **Durable Execution**: Workflows survive process and infrastructure failures
|
||||
- **Language Support**: SDKs for Go, Java, Python, TypeScript, .NET, PHP
|
||||
- **Visibility**: Query and observe workflow state via Web UI and APIs
|
||||
- **Scalability**: Horizontally scalable architecture
|
||||
- **Multi-tenancy**: Namespace-based isolation for workflows
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Kubernetes cluster (k3s)
|
||||
- PostgreSQL cluster (CloudNativePG)
|
||||
- Keycloak installed and configured
|
||||
- Vault for secrets management
|
||||
- External Secrets Operator (optional, for Vault integration)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
just temporal::install
|
||||
```
|
||||
|
||||
You will be prompted for:
|
||||
|
||||
- **Temporal host (FQDN)**: e.g., `temporal.example.com`
|
||||
- **Keycloak host (FQDN)**: e.g., `auth.example.com`
|
||||
- **Enable Prometheus monitoring**: If kube-prometheus-stack is installed
|
||||
|
||||
### What Gets Installed
|
||||
|
||||
- Temporal Server (frontend, history, matching, worker services)
|
||||
- Temporal Web UI with Keycloak OIDC authentication
|
||||
- Temporal Admin Tools for cluster management
|
||||
- PostgreSQL databases (`temporal`, `temporal_visibility`)
|
||||
- Keycloak OAuth client (confidential client)
|
||||
- Vault secrets (if External Secrets Operator is available)
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables (set in `.env.local` or override):
|
||||
|
||||
| Variable | Default | Description |
|
||||
| -------- | ------- | ----------- |
|
||||
| `TEMPORAL_NAMESPACE` | `temporal` | Kubernetes namespace |
|
||||
| `TEMPORAL_CHART_VERSION` | `0.52.0` | Helm chart version |
|
||||
| `TEMPORAL_HOST` | (prompt) | External hostname (FQDN) |
|
||||
| `TEMPORAL_OIDC_CLIENT_ID` | `temporal` | Keycloak client ID |
|
||||
| `KEYCLOAK_HOST` | (prompt) | Keycloak hostname (FQDN) |
|
||||
| `KEYCLOAK_REALM` | `buunstack` | Keycloak realm |
|
||||
| `MONITORING_ENABLED` | (prompt) | Enable Prometheus ServiceMonitor |
|
||||
|
||||
## Architecture
|
||||
|
||||
```plain
|
||||
External Users
|
||||
|
|
||||
Cloudflare Tunnel (HTTPS)
|
||||
|
|
||||
Traefik Ingress (HTTPS)
|
||||
|
|
||||
Temporal Web UI (HTTP inside cluster)
|
||||
|-- OAuth --> Keycloak (authentication)
|
||||
|
|
||||
Temporal Server
|
||||
|-- Frontend Service (gRPC :7233)
|
||||
| |-- Client connections
|
||||
| |-- Workflow/Activity APIs
|
||||
|
|
||||
|-- History Service
|
||||
| |-- Workflow state management
|
||||
| |-- Event sourcing
|
||||
|
|
||||
|-- Matching Service
|
||||
| |-- Task queue management
|
||||
| |-- Worker polling
|
||||
|
|
||||
|-- Worker Service
|
||||
| |-- System workflows
|
||||
| |-- Archival
|
||||
|
|
||||
PostgreSQL (temporal, temporal_visibility)
|
||||
```
|
||||
|
||||
**Key Components**:
|
||||
|
||||
- **Frontend**: Entry point for all client requests (gRPC API)
|
||||
- **History**: Maintains workflow execution history and state
|
||||
- **Matching**: Routes tasks to appropriate workers
|
||||
- **Worker**: Executes internal system workflows
|
||||
- **Web UI**: Browser-based workflow monitoring and management
|
||||
- **Admin Tools**: CLI tools for cluster administration
|
||||
|
||||
## Usage
|
||||
|
||||
### Access Web UI
|
||||
|
||||
1. Navigate to `https://your-temporal-host/`
|
||||
2. Authenticate via Keycloak SSO
|
||||
3. Select a namespace to view workflows
|
||||
|
||||
### Temporal CLI Setup (Local Development)
|
||||
|
||||
The Temporal gRPC endpoint is only accessible within the cluster network. Use [Telepresence](https://www.telepresence.io/) to connect from your local machine.
|
||||
|
||||
#### Step 1: Connect to the Cluster
|
||||
|
||||
```bash
|
||||
telepresence connect
|
||||
```
|
||||
|
||||
#### Step 2: Configure Temporal CLI
|
||||
|
||||
Set environment variables (add to `.bashrc`, `.zshrc`, or use direnv):
|
||||
|
||||
```bash
|
||||
export TEMPORAL_ADDRESS="temporal-frontend.temporal:7233"
|
||||
export TEMPORAL_NAMESPACE="default"
|
||||
```
|
||||
|
||||
Or create a named environment for multiple clusters:
|
||||
|
||||
```bash
|
||||
# Configure named environment
|
||||
temporal env set --env buun -k address -v temporal-frontend.temporal:7233
|
||||
temporal env set --env buun -k namespace -v default
|
||||
|
||||
# Use with commands
|
||||
temporal workflow list --env buun
|
||||
```
|
||||
|
||||
#### Step 3: Verify Connection
|
||||
|
||||
```bash
|
||||
# Check telepresence status
|
||||
telepresence status
|
||||
|
||||
# Test Temporal connection
|
||||
temporal operator namespace list
|
||||
```
|
||||
|
||||
#### CLI Examples
|
||||
|
||||
```bash
|
||||
# List workflows
|
||||
temporal workflow list
|
||||
|
||||
# Describe a workflow
|
||||
temporal workflow describe --workflow-id my-workflow-id
|
||||
|
||||
# Query workflow state
|
||||
temporal workflow query --workflow-id my-workflow-id --type my-query
|
||||
|
||||
# Signal a workflow
|
||||
temporal workflow signal --workflow-id my-workflow-id --name my-signal
|
||||
|
||||
# Terminate a workflow
|
||||
temporal workflow terminate --workflow-id my-workflow-id --reason "manual termination"
|
||||
```
|
||||
|
||||
### Create a Temporal Namespace
|
||||
|
||||
Before running workflows, create a namespace:
|
||||
|
||||
```bash
|
||||
just temporal::create-temporal-namespace default
|
||||
```
|
||||
|
||||
With custom retention period:
|
||||
|
||||
```bash
|
||||
just temporal::create-temporal-namespace myapp 7d
|
||||
```
|
||||
|
||||
### List Temporal Namespaces
|
||||
|
||||
```bash
|
||||
just temporal::list-temporal-namespaces
|
||||
```
|
||||
|
||||
### Cluster Health Check
|
||||
|
||||
```bash
|
||||
just temporal::cluster-info
|
||||
```
|
||||
|
||||
### Connect Workers
|
||||
|
||||
Workers connect to the Temporal Frontend service. From within the cluster:
|
||||
|
||||
```text
|
||||
temporal-frontend.temporal:7233
|
||||
```
|
||||
|
||||
Example Python worker:
|
||||
|
||||
```python
|
||||
from temporalio.client import Client
|
||||
from temporalio.worker import Worker
|
||||
|
||||
async def main():
|
||||
client = await Client.connect("temporal-frontend.temporal:7233")
|
||||
|
||||
worker = Worker(
|
||||
client,
|
||||
task_queue="my-task-queue",
|
||||
workflows=[MyWorkflow],
|
||||
activities=[my_activity],
|
||||
)
|
||||
await worker.run()
|
||||
```
|
||||
|
||||
Example Go worker:
|
||||
|
||||
```go
|
||||
import (
|
||||
"go.temporal.io/sdk/client"
|
||||
"go.temporal.io/sdk/worker"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c, _ := client.Dial(client.Options{
|
||||
HostPort: "temporal-frontend.temporal:7233",
|
||||
})
|
||||
defer c.Close()
|
||||
|
||||
w := worker.New(c, "my-task-queue", worker.Options{})
|
||||
w.RegisterWorkflow(MyWorkflow)
|
||||
w.RegisterActivity(MyActivity)
|
||||
w.Run(worker.InterruptCh())
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### Web UI (OIDC)
|
||||
|
||||
- Users authenticate via Keycloak
|
||||
- Standard OIDC flow with Authorization Code grant
|
||||
- Configured via environment variables in the Web UI deployment
|
||||
|
||||
### gRPC API
|
||||
|
||||
- By default, no authentication is required for gRPC connections within the cluster
|
||||
- For production, configure mTLS or JWT-based authorization
|
||||
|
||||
## Management
|
||||
|
||||
### Upgrade Temporal
|
||||
|
||||
```bash
|
||||
just temporal::upgrade
|
||||
```
|
||||
|
||||
### Uninstall
|
||||
|
||||
```bash
|
||||
just temporal::uninstall
|
||||
```
|
||||
|
||||
This removes:
|
||||
|
||||
- Helm release and all Kubernetes resources
|
||||
- Namespace
|
||||
- Keycloak client
|
||||
|
||||
**Note**: The following resources are NOT deleted:
|
||||
|
||||
- PostgreSQL databases (`temporal`, `temporal_visibility`)
|
||||
- Vault secrets
|
||||
|
||||
### Full Cleanup
|
||||
|
||||
To remove everything including databases and Vault secrets:
|
||||
|
||||
```bash
|
||||
just temporal::uninstall true
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
just temporal::delete-postgres-user-and-db
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Check Pod Status
|
||||
|
||||
```bash
|
||||
kubectl get pods -n temporal
|
||||
```
|
||||
|
||||
Expected pods:
|
||||
|
||||
- `temporal-frontend-*` - Frontend service
|
||||
- `temporal-history-*` - History service
|
||||
- `temporal-matching-*` - Matching service
|
||||
- `temporal-worker-*` - Worker service
|
||||
- `temporal-web-*` - Web UI
|
||||
- `temporal-admintools-*` - Admin tools
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# Frontend logs
|
||||
kubectl logs -n temporal deployment/temporal-frontend --tail=100
|
||||
|
||||
# History logs
|
||||
kubectl logs -n temporal deployment/temporal-history --tail=100
|
||||
|
||||
# Web UI logs
|
||||
kubectl logs -n temporal deployment/temporal-web --tail=100
|
||||
```
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
Check PostgreSQL connectivity:
|
||||
|
||||
```bash
|
||||
kubectl exec -n temporal deployment/temporal-admintools -- \
|
||||
psql -h postgres-cluster-rw.postgres -U temporal -d temporal -c "SELECT 1"
|
||||
```
|
||||
|
||||
### Schema Issues
|
||||
|
||||
If schema initialization fails, check the schema job:
|
||||
|
||||
```bash
|
||||
kubectl logs -n temporal -l app.kubernetes.io/component=schema --all-containers
|
||||
```
|
||||
|
||||
### Service Discovery Issues
|
||||
|
||||
Verify services are running:
|
||||
|
||||
```bash
|
||||
kubectl get svc -n temporal
|
||||
```
|
||||
|
||||
Test frontend connectivity from admin tools:
|
||||
|
||||
```bash
|
||||
kubectl exec -n temporal deployment/temporal-admintools -- \
|
||||
tctl cluster health
|
||||
```
|
||||
|
||||
### Web UI Login Issues
|
||||
|
||||
Verify Keycloak client configuration:
|
||||
|
||||
```bash
|
||||
just keycloak::get-client buunstack temporal
|
||||
```
|
||||
|
||||
Check Web UI environment variables:
|
||||
|
||||
```bash
|
||||
kubectl get deployment temporal-web -n temporal -o jsonpath='{.spec.template.spec.containers[0].env}' | jq
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
| File | Description |
|
||||
| ---- | ----------- |
|
||||
| `temporal-values.gomplate.yaml` | Helm values template |
|
||||
| `postgres-external-secret.gomplate.yaml` | PostgreSQL credentials ExternalSecret |
|
||||
| `keycloak-auth-external-secret.gomplate.yaml` | Keycloak OIDC credentials ExternalSecret |
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Pod Security Standards**: Namespace configured with **baseline** enforcement
|
||||
- **Server Security**: Temporal server components run with restricted-compliant security contexts
|
||||
|
||||
### Why Not Restricted?
|
||||
|
||||
The namespace cannot use `restricted` Pod Security Standards due to the Temporal Web UI image (`temporalio/ui`):
|
||||
|
||||
- The image writes configuration files to `./config/docker.yaml` at startup
|
||||
- The container's filesystem is owned by root (UID 0)
|
||||
- When running as non-root user (UID 1000), the container cannot write to these paths
|
||||
- Error: `unable to create open ./config/docker.yaml: permission denied`
|
||||
|
||||
The Temporal server components (frontend, history, matching, worker) **do** meet `restricted` requirements and run with full security hardening. Only the Web UI component requires `baseline`.
|
||||
|
||||
### Server Security Context
|
||||
|
||||
Temporal server components (frontend, history, matching, worker) run with:
|
||||
|
||||
- `runAsNonRoot: true`
|
||||
- `runAsUser: 1000`
|
||||
- `allowPrivilegeEscalation: false`
|
||||
- `seccompProfile.type: RuntimeDefault`
|
||||
- `capabilities.drop: [ALL]`
|
||||
|
||||
## References
|
||||
|
||||
- [Temporal Documentation](https://docs.temporal.io/)
|
||||
- [Temporal GitHub](https://github.com/temporalio/temporal)
|
||||
- [Temporal Helm Charts](https://github.com/temporalio/helm-charts)
|
||||
Reference in New Issue
Block a user