Compare commits
2 Commits
main
...
afd-caddy-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8426d99ed9 | ||
|
|
3606b5dd90 |
@ -0,0 +1,250 @@
|
||||
# Caddy Ingress Controller for kind
|
||||
# Based on: https://github.com/caddyserver/ingress
|
||||
# Provides automatic HTTPS with Let's Encrypt
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: caddy-system
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: caddy-ingress-controller
|
||||
namespace: caddy-system
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: caddy-ingress-controller
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
- endpoints
|
||||
- nodes
|
||||
- pods
|
||||
- secrets
|
||||
- namespaces
|
||||
- services
|
||||
verbs:
|
||||
- list
|
||||
- watch
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses/status
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingressclasses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- update
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: caddy-ingress-controller
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: caddy-ingress-controller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: caddy-ingress-controller
|
||||
namespace: caddy-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: caddy-ingress-controller-configmap
|
||||
namespace: caddy-system
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
data:
|
||||
# Caddy global options
|
||||
acmeCA: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
email: ""
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: caddy-ingress-controller
|
||||
namespace: caddy-system
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
app.kubernetes.io/component: controller
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: https
|
||||
protocol: TCP
|
||||
selector:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
app.kubernetes.io/component: controller
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: caddy-ingress-controller
|
||||
namespace: caddy-system
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
app.kubernetes.io/component: controller
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
app.kubernetes.io/component: controller
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
app.kubernetes.io/component: controller
|
||||
spec:
|
||||
serviceAccountName: caddy-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
nodeSelector:
|
||||
ingress-ready: "true"
|
||||
kubernetes.io/os: linux
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/master
|
||||
operator: Equal
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/control-plane
|
||||
operator: Equal
|
||||
containers:
|
||||
- name: caddy-ingress-controller
|
||||
image: ghcr.io/caddyserver/ingress:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
hostPort: 80
|
||||
protocol: TCP
|
||||
- name: https
|
||||
containerPort: 443
|
||||
hostPort: 443
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
args:
|
||||
- -config-map=caddy-system/caddy-ingress-controller-configmap
|
||||
- -class-name=caddy
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 512Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 9765
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 9765
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 10
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
capabilities:
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
drop:
|
||||
- ALL
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
volumeMounts:
|
||||
- name: caddy-data
|
||||
mountPath: /data
|
||||
- name: caddy-config
|
||||
mountPath: /config
|
||||
volumes:
|
||||
- name: caddy-data
|
||||
emptyDir: {}
|
||||
- name: caddy-config
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: IngressClass
|
||||
metadata:
|
||||
name: caddy
|
||||
labels:
|
||||
app.kubernetes.io/name: caddy-ingress-controller
|
||||
app.kubernetes.io/instance: caddy-ingress
|
||||
annotations:
|
||||
ingressclass.kubernetes.io/is-default-class: "true"
|
||||
spec:
|
||||
controller: caddy.io/ingress-controller
|
||||
@ -42,6 +42,7 @@ from stack_orchestrator.deploy.deployment_context import DeploymentContext
|
||||
from stack_orchestrator.deploy.deployment_create import create as deployment_create
|
||||
from stack_orchestrator.deploy.deployment_create import init as deployment_init
|
||||
from stack_orchestrator.deploy.deployment_create import setup as deployment_setup
|
||||
from stack_orchestrator.deploy.k8s import k8s_command
|
||||
|
||||
|
||||
@click.group()
|
||||
@ -54,6 +55,10 @@ from stack_orchestrator.deploy.deployment_create import setup as deployment_setu
|
||||
def command(ctx, include, exclude, env_file, cluster, deploy_to):
|
||||
'''deploy a stack'''
|
||||
|
||||
# k8s subcommand doesn't require stack
|
||||
if ctx.invoked_subcommand == "k8s":
|
||||
return
|
||||
|
||||
# Although in theory for some subcommands (e.g. deploy create) the stack can be inferred,
|
||||
# Click doesn't allow us to know that here, so we make providing the stack mandatory
|
||||
stack = global_options2(ctx).stack
|
||||
@ -460,3 +465,4 @@ def _orchestrate_cluster_config(ctx, cluster_config, deployer, container_exec_en
|
||||
command.add_command(deployment_init)
|
||||
command.add_command(deployment_create)
|
||||
command.add_command(deployment_setup)
|
||||
command.add_command(k8s_command.command, "k8s")
|
||||
|
||||
@ -162,10 +162,12 @@ class ClusterInfo:
|
||||
)
|
||||
|
||||
ingress_annotations = {
|
||||
"kubernetes.io/ingress.class": "nginx",
|
||||
"kubernetes.io/ingress.class": "caddy",
|
||||
}
|
||||
if not certificate:
|
||||
ingress_annotations["cert-manager.io/cluster-issuer"] = cluster_issuer
|
||||
# Note: Caddy handles TLS automatically via Let's Encrypt, no cert-manager needed
|
||||
if not certificate and cluster_issuer:
|
||||
# Only add cert-manager annotation if using nginx ingress with cert-manager
|
||||
pass # Caddy handles certificates automatically
|
||||
|
||||
ingress = client.V1Ingress(
|
||||
metadata=client.V1ObjectMeta(
|
||||
|
||||
@ -210,8 +210,12 @@ class K8sDeployer(Deployer):
|
||||
self.skip_cluster_management = skip_cluster_management
|
||||
if not opts.o.dry_run:
|
||||
if self.is_kind() and not self.skip_cluster_management:
|
||||
# Create the kind cluster
|
||||
create_cluster(self.kind_cluster_name, self.deployment_dir.joinpath(constants.kind_config_filename))
|
||||
# Create the kind cluster (or reuse existing one)
|
||||
kind_config = self.deployment_dir.joinpath(constants.kind_config_filename)
|
||||
actual_cluster = create_cluster(self.kind_cluster_name, kind_config)
|
||||
if actual_cluster != self.kind_cluster_name:
|
||||
# An existing cluster was found, use it instead
|
||||
self.kind_cluster_name = actual_cluster
|
||||
# Ensure the referenced containers are copied into kind
|
||||
load_images_into_kind(self.kind_cluster_name, self.cluster_info.image_set)
|
||||
self.connect_api()
|
||||
|
||||
@ -35,40 +35,106 @@ def _run_command(command: str):
|
||||
return result
|
||||
|
||||
|
||||
def get_kind_cluster():
|
||||
"""Get an existing kind cluster, if any.
|
||||
|
||||
Uses `kind get clusters` to find existing clusters.
|
||||
Returns the cluster name or None if no cluster exists.
|
||||
"""
|
||||
result = subprocess.run(
|
||||
"kind get clusters",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
|
||||
clusters = result.stdout.strip().splitlines()
|
||||
if clusters:
|
||||
return clusters[0] # Return the first cluster found
|
||||
return None
|
||||
|
||||
|
||||
def create_cluster(name: str, config_file: str):
|
||||
"""Create a kind cluster, or reuse an existing one.
|
||||
|
||||
Checks if any kind cluster already exists. If so, uses that cluster
|
||||
instead of creating a new one. This allows multiple deployments to
|
||||
share the same kind cluster.
|
||||
|
||||
Args:
|
||||
name: The desired cluster name (used only if creating new)
|
||||
config_file: Path to kind config file (used only if creating new)
|
||||
|
||||
Returns:
|
||||
The name of the cluster being used (either existing or newly created)
|
||||
"""
|
||||
existing = get_kind_cluster()
|
||||
if existing:
|
||||
print(f"Using existing cluster: {existing}")
|
||||
return existing
|
||||
|
||||
print(f"Creating new cluster: {name}")
|
||||
result = _run_command(f"kind create cluster --name {name} --config {config_file}")
|
||||
if result.returncode != 0:
|
||||
raise DeployerException(f"kind create cluster failed: {result}")
|
||||
return name
|
||||
|
||||
|
||||
def destroy_cluster(name: str):
|
||||
_run_command(f"kind delete cluster --name {name}")
|
||||
|
||||
|
||||
def wait_for_ingress_in_kind():
|
||||
def wait_for_ingress_in_kind(ingress_type="caddy"):
|
||||
"""Wait for ingress controller to become ready.
|
||||
|
||||
Args:
|
||||
ingress_type: "caddy" or "nginx" - determines which namespace and labels to check
|
||||
"""
|
||||
core_v1 = client.CoreV1Api()
|
||||
|
||||
if ingress_type == "caddy":
|
||||
namespace = "caddy-system"
|
||||
label_selector = "app.kubernetes.io/component=controller"
|
||||
else:
|
||||
namespace = "ingress-nginx"
|
||||
label_selector = "app.kubernetes.io/component=controller"
|
||||
|
||||
for i in range(20):
|
||||
warned_waiting = False
|
||||
w = watch.Watch()
|
||||
for event in w.stream(func=core_v1.list_namespaced_pod,
|
||||
namespace="ingress-nginx",
|
||||
label_selector="app.kubernetes.io/component=controller",
|
||||
namespace=namespace,
|
||||
label_selector=label_selector,
|
||||
timeout_seconds=30):
|
||||
if event['object'].status.container_statuses:
|
||||
if event['object'].status.container_statuses[0].ready is True:
|
||||
if warned_waiting:
|
||||
print("Ingress controller is ready")
|
||||
print(f"{ingress_type.capitalize()} ingress controller is ready")
|
||||
return
|
||||
print("Waiting for ingress controller to become ready...")
|
||||
print(f"Waiting for {ingress_type} ingress controller to become ready...")
|
||||
warned_waiting = True
|
||||
error_exit("ERROR: Timed out waiting for ingress to become ready")
|
||||
error_exit(f"ERROR: Timed out waiting for {ingress_type} ingress to become ready")
|
||||
|
||||
|
||||
def install_ingress_for_kind():
|
||||
def install_ingress_for_kind(ingress_type="caddy"):
|
||||
"""Install ingress controller in kind cluster.
|
||||
|
||||
Args:
|
||||
ingress_type: "caddy" or "nginx" - determines which ingress controller to install
|
||||
"""
|
||||
api_client = client.ApiClient()
|
||||
ingress_install = os.path.abspath(get_k8s_dir().joinpath("components", "ingress", "ingress-nginx-kind-deploy.yaml"))
|
||||
if opts.o.debug:
|
||||
print("Installing nginx ingress controller in kind cluster")
|
||||
|
||||
if ingress_type == "caddy":
|
||||
ingress_install = os.path.abspath(get_k8s_dir().joinpath("components", "ingress", "ingress-caddy-kind-deploy.yaml"))
|
||||
if opts.o.debug:
|
||||
print("Installing Caddy ingress controller in kind cluster")
|
||||
else:
|
||||
ingress_install = os.path.abspath(get_k8s_dir().joinpath("components", "ingress", "ingress-nginx-kind-deploy.yaml"))
|
||||
if opts.o.debug:
|
||||
print("Installing nginx ingress controller in kind cluster")
|
||||
|
||||
utils.create_from_yaml(api_client, yaml_file=ingress_install)
|
||||
|
||||
|
||||
@ -251,9 +317,9 @@ def _generate_kind_port_mappings_from_services(parsed_pod_files):
|
||||
|
||||
def _generate_kind_port_mappings(parsed_pod_files):
|
||||
port_definitions = []
|
||||
# For now we just map port 80 for the nginx ingress controller we install in kind
|
||||
port_string = "80"
|
||||
port_definitions.append(f" - containerPort: {port_string}\n hostPort: {port_string}\n")
|
||||
# Map port 80 (HTTP) and 443 (HTTPS) for the ingress controller
|
||||
for port_string in ["80", "443"]:
|
||||
port_definitions.append(f" - containerPort: {port_string}\n hostPort: {port_string}\n")
|
||||
return (
|
||||
"" if len(port_definitions) == 0 else (
|
||||
" extraPortMappings:\n"
|
||||
|
||||
43
stack_orchestrator/deploy/k8s/k8s_command.py
Normal file
43
stack_orchestrator/deploy/k8s/k8s_command.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Copyright © 2024 Vulcanize
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
||||
|
||||
import click
|
||||
|
||||
from stack_orchestrator.deploy.k8s.helpers import get_kind_cluster
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.pass_context
|
||||
def command(ctx):
|
||||
'''k8s cluster management commands'''
|
||||
pass
|
||||
|
||||
|
||||
@command.group()
|
||||
@click.pass_context
|
||||
def list(ctx):
|
||||
'''list k8s resources'''
|
||||
pass
|
||||
|
||||
|
||||
@list.command()
|
||||
@click.pass_context
|
||||
def cluster(ctx):
|
||||
'''Show the existing kind cluster'''
|
||||
existing_cluster = get_kind_cluster()
|
||||
if existing_cluster:
|
||||
print(existing_cluster)
|
||||
else:
|
||||
print("No cluster found")
|
||||
Loading…
Reference in New Issue
Block a user