This file provides guidance to coding agents when working with code in this repository.
ictsc-k8s-infra is a production-ready Kubernetes infrastructure project for ICTSC. It manages k0s Kubernetes clusters (dev and prod) on Sakura Cloud with:
- Networking: IPv4/IPv6 dual-stack, NAT64 translation, Cilium CNI with Gateway API support
- Application Deployment: ArgoCD for GitOps-based continuous delivery
- Storage: TopoLVM CSI driver for local storage, CloudNativePG for PostgreSQL
- Security: cert-manager for certificate management, CSI Secrets Store for secret injection
- Observability: OpenTelemetry Collector for metrics, Hubble UI for Cilium network visibility
- Infrastructure: Terraform-managed infrastructure, Ansible-automated cluster setup
- Infrastructure as Code: Terraform (Sakura Cloud provider, S3 backend)
- Kubernetes: k0s lightweight distribution with Cilium CNI
- Configuration Management: Ansible with uv for Python package management
- Manifest Management: Kustomize with Helm Chart integration
- CLI Tools: aqua for declarative version management
- tenv (Terraform/OpenTofu manager), uv (Python), usacloud (Sakura Cloud CLI)
- kustomize, gh (GitHub CLI), cosign (container signing), trivy (security scanning)
- Networking:
- Cilium CNI with Gateway API, Envoy Gateway
- IPv4/IPv6 dual-stack with NAT64 prefix translation
- L2 announcement for LoadBalancer services
- Storage: TopoLVM CSI driver (local storage), CloudNativePG operator (PostgreSQL)
- Security: cert-manager (TLS certificates), CSI Secrets Store, Sakura Cloud Secret Manager
- GitOps: ArgoCD for continuous delivery
- Observability: OpenTelemetry Collector, Hubble UI, telemetry stack
# Initialize Terraform (requires S3 backend credentials via environment)
make tf-init
# Plan infrastructure changes
make tf-plan
# Apply infrastructure changes
make tf-apply
# Format and validate Terraform code
make tf-fmt
make tf-validate
make validate # Runs both tf-validate and ansible-validate# Generate Kubernetes manifests and run Ansible playbook (default: dev environment)
make ansible-apply
# Run Ansible playbook for a specific environment
ENV=staging make ansible-apply
ENV=prod make ansible-apply
# Validate Ansible playbooks
make ansible-validate# Generate all Kustomize manifests (creates manifests/*/dev.generated.yaml)
make manifests
# Clean generated manifests
make clean-manifestsThe infrastructure is managed via Terraform with two environments (dev/prod). The k8s_nodes module (terraform/modules/k8s_nodes/) provisions:
- NAT64 Box: Single node handling IPv6-to-IPv4 translation
- Control Plane: Configurable number of nodes (default: 3, must be odd) running k0s API server
- Worker Nodes: Configurable number of nodes (default: 3) for workload scheduling
- Load Balancer: Multiple IPv4 addresses for service ingress (default: 4)
- Network: IPv6 subnets allocated via CIDR subnetting for NAT64 box, control plane, and worker groups
Terraform Modules:
k8s_nodes: Provisions Sakura Cloud infrastructure (servers, networks, load balancers)ansible_inventory: Generates dynamic Ansible inventory from infrastructure outputs
Environment-Specific Configuration (terraform/env/{dev,prod}/main.tf):
- Separate S3 backend state files per environment
- Environment-specific cluster names (ictsc-dev, ictsc-prod)
- Environment-specific API hostnames (k8s-dev.ictsc.net, k8s-prod.ictsc.net)
- Environment-specific Secret Manager vault IDs
Key Terraform Outputs:
ansible_inventory: Complete Ansible inventory with all hosts, groups, and variablesk8s_api_host: Kubernetes API server hostname (e.g., k8s-dev.ictsc.net)k8s_api_ipv4,k8s_api_ipv6: Kubernetes API server IP addressesweb_ipv4: First LoadBalancer IPv4 address (for web ingress)web_ipv6: LoadBalancer IPv6 address (calculated from CIDR)
ansible/setup.yaml orchestrates cluster setup in stages:
- OpenTelemetry Setup (all hosts): Installs metrics collection agent
- NAT64 Configuration (nat64box group): Sets up IPv6-to-IPv4 translation
- k0s Installation (kubernetes group): Installs k0s binaries
- Control Plane Config (cplane group): Swap configuration, firewall rules, k0s cluster configuration
- Bootstrap First Controller (cplane:&bootstrap): Initializes the cluster and sets up backup
- Join Additional Controllers (cplane:!bootstrap): High availability setup
- Setup Worker Nodes (worker group): Worker-specific system configuration
- Join Workers (worker group): Registers worker nodes to cluster
Key Ansible roles in ansible/roles/:
install_k0s/: Downloads and installs k0s binariesk0s/: Generates and applies k0s cluster configurationbootstrap_controller/: Initializes the first control plane nodejoin_controller/,join_worker/: Node joining automationcplane_firewall/: Control plane network policiesotelcol/: OpenTelemetry Collector daemon setupnat64box/: NAT64 box configurationbackup_k0s/: k0s state backup functionalityreset_k0s/: Cluster reset automationsetup_swap/: Swap configuration for nodessetup_worker/: Worker node setup and configuration
Manifests use Kustomize with base and overlay pattern. The project has 14 manifest directories:
Core Infrastructure:
cilium/: Cilium CNI with Helm Chart (base, dev, prod overlays)cilium-extra/: Additional Cilium configurationscoredns/: CoreDNS configurationrbac/: RBAC policies
Application Deployment:
argocd-install/: ArgoCD installation (with CRDs)argocd-apps/: ArgoCD application definitionsargocd-extra/: Additional ArgoCD configurations
Networking:
gateway/: Gateway API resourcesenvoy-gateway/: Envoy Gateway configuration (with CRDs)
Security & Secrets:
cert-manager/: Certificate management (with CRDs)csi-secrets-store/: CSI Secrets Store driver (with CRDs)
Storage & Database:
topolvm/: TopoLVM CSI driver (with CRDs)cloudnative-pg/: CloudNativePG operator (with CRDs)
Observability:
telemetry/: OpenTelemetry and observability stack (with CRDs)
Each manifest directory follows the pattern:
manifests/<component>/
├── base/ # Base resources
├── crds/ # CRDs (if applicable)
├── components/ # Reusable components (if applicable)
├── dev/ # Development overlay
└── prod/ # Production overlay (if applicable)
Generated manifests are created via kustomize build --enable-helm --load-restrictor LoadRestrictionsNone:
manifests/*/crds.generated.yaml: CRD definitions (where applicable)manifests/*/dev.generated.yaml: Development environment manifestsmanifests/*/prod.generated.yaml: Production environment manifests (where applicable)
Manifests are generated during make manifests and applied during make ansible-apply.
The following environment variables are used by the infrastructure:
Build Environment:
ENV: Target environment (default:dev, supports:dev,prod)AWS_REQUEST_CHECKSUM_CALCULATION: S3 backend configuration (set towhen_requiredin Makefile)
The project uses Sakura Cloud Secret Manager for sensitive data via a custom Ansible lookup plugin (ansible/plugins/lookup/sakura_secret.py). Secrets are retrieved dynamically during playbook execution:
Secrets stored in Sakura Secret Manager (referenced in ansible/setup.yaml):
sakuracloud-metrics-endpoint: OpenTelemetry metrics ingestion endpointsakuracloud-metrics-token: OpenTelemetry authentication tokensakuracloud-secret-access-token: Sakura Cloud access tokensakuracloud-secret-access-token-secret: Sakura Cloud access token secretsakuracloud-s3-backup-access-key-id: S3 backup access key IDsakuracloud-s3-backup-secret-access-key: S3 backup secret access key
Secret Manager Configuration:
- Vault ID is passed via Terraform output (different for dev/prod)
- Secrets are environment-specific (managed via
envvariable) - Access requires Sakura Cloud API credentials
No manual environment variable configuration is required for secrets; they are automatically retrieved from Secret Manager during Ansible execution.
-
Dual Environment Setup: The project supports both
devandprodenvironments- Each has separate Terraform state files and Secret Manager vaults
- Default environment is
dev; useENV=prodfor production operations - Ensure you're operating on the correct environment to avoid accidental changes
-
Terraform State: Uses S3 backend on Sakura Storage (endpoint:
s3.isk01.sakurastorage.jp)- Separate state files:
ictsc-k8s-dev.tfstateandictsc-k8s-prod.tfstate - Backend configuration in
terraform/env/{dev,prod}/main.tf - Requires S3-compatible credentials in environment
- Separate state files:
-
Ansible Inventory: Dynamically generated from Terraform outputs via
ansible/inventory.sh- Reads from
terraform -chdir=../terraform/env/${ENV} output -raw ansible_inventory - Includes all host groups, variables, and Sakura Secret Manager vault ID
- Reads from
-
Manifest Generation: Run
make manifestsbeforemake ansible-apply- Generates CRDs and environment-specific manifests (dev/prod)
- Uses
kustomize build --enable-helm --load-restrictor LoadRestrictionsNone - Total of ~35 generated manifest files across 14 components
-
Secret Management: Secrets are stored in Sakura Cloud Secret Manager
- Retrieved dynamically via custom lookup plugin (
ansible/plugins/lookup/sakura_secret.py) - Environment-specific vaults (different vault IDs for dev/prod)
- No need to set environment variables for secrets
- Retrieved dynamically via custom lookup plugin (
-
k0s Configuration: Cluster configuration is templated and applied to control plane nodes
- Configuration in
k0srole with extensive variables - Changes require re-running Ansible playbook
- Control plane node count must be odd (minimum 1, default 3)
- Configuration in
-
Control Plane HA: Multi-node control plane for high availability
- First controller bootstrapped with
bootstrap_controllerrole - Additional controllers join via
join_controllerrole - Backup configured via
backup_k0srole
- First controller bootstrapped with
-
GitOps with ArgoCD: ArgoCD manages application deployments
- Installation via
argocd-installmanifests (includes CRDs) - Application definitions in
argocd-apps - Extra configurations in
argocd-extra
- Installation via
-
Cluster Reset:
ansible/reset_k0s.yamlplaybook can reset the cluster- Use with extreme caution, especially in production
- Completely wipes k0s state and requires re-bootstrap
-
Git Commits: Always verify staged files before committing:
- Do NOT use
git add -A(includes untracked files) - Run
git statusandgit diff --cachedto verify what will be committed - Only commit explicitly staged files
- Use
git restore --staged <file>to unstage unwanted files
terraform/env/: Environment-specific Terraform configurationsdev/: Development environment configurationprod/: Production environment configuration
terraform/modules/: Reusable Terraform modulesk8s_nodes/: Infrastructure module for cluster provisioningansible_inventory/: Dynamic Ansible inventory generation
ansible/: Cluster setup automationroles/: 12 Ansible roles for cluster configurationplugins/lookup/: Custom lookup plugin for Sakura Secret Managersetup.yaml: Main playbook for cluster setupreset_k0s.yaml: Cluster reset playbookpyproject.toml&uv.lock: Python dependencies managed via uv
manifests/: 14 Kubernetes component directories using Kustomize- Each with base/, dev/, prod/ overlays (and crds/, components/ where applicable)
Makefile: Command wrappers for Terraform, Ansible, and Kustomize operationsaqua.yaml: Declarative CLI tool version management (tenv, uv, kustomize, usacloud, etc.).github/: GitHub Actions workflows for CI/CD (actionlint, autofix, trivy scanning)
- Environment: Two environments are available:
dev(default) andprod- Override with
ENV=prod make tf-planorENV=prod make ansible-apply - Each environment has its own Terraform state and Sakura Secret Manager vault
- Override with
- Node Counts: Configure
cplane_nodesandworker_nodesin respectiveterraform/env/{dev,prod}/main.tf- Control plane nodes must be an odd number (default: 3)
- Worker nodes minimum 1 (default: 3)
- Network Configuration:
- Modify variables passed to
k8s_nodesmodule in environment-specificmain.tf - IPv6 CIDR subnetting for NAT64 box, control plane, and worker nodes
- LoadBalancer IPv4 address count (default: 4)
- Modify variables passed to
- Cilium Configuration: Edit
manifests/cilium/{dev,prod}/kustomization.yamlto customize Helm values - Manifest Overlays: Each component supports dev/prod overlays for environment-specific configuration
- Manifest Generation: Run
make manifeststo generate all manifests (CRDs, dev, and prod)