Files
buun-stack/temporal/README.md
2025-12-07 16:18:50 +09:00

402 lines
9.4 KiB
Markdown

# 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)