Archived
Support non-tls ingress for kind (#748)
Lint Checks / Run linter (push) Successful in 39s
Publish / Build and publish (push) Successful in 1m19s
Webapp Test / Run webapp test suite (push) Successful in 4m40s
Deploy Test / Run deploy test suite (push) Successful in 4m58s
Smoke Test / Run basic test suite (push) Successful in 4m44s
Lint Checks / Run linter (push) Successful in 39s
Publish / Build and publish (push) Successful in 1m19s
Webapp Test / Run webapp test suite (push) Successful in 4m40s
Deploy Test / Run deploy test suite (push) Successful in 4m58s
Smoke Test / Run basic test suite (push) Successful in 4m44s
Reviewed-on: #748 Co-authored-by: David Boreham <david@bozemanpass.com> Co-committed-by: David Boreham <david@bozemanpass.com>
This commit was merged in pull request #748.
This commit is contained in:
@@ -78,7 +78,30 @@ class ClusterInfo:
|
||||
if (opts.o.debug):
|
||||
print(f"Env vars: {self.environment_variables.map}")
|
||||
|
||||
def get_ingress(self):
|
||||
def get_nodeport(self):
|
||||
for pod_name in self.parsed_pod_yaml_map:
|
||||
pod = self.parsed_pod_yaml_map[pod_name]
|
||||
services = pod["services"]
|
||||
for service_name in services:
|
||||
service_info = services[service_name]
|
||||
if "ports" in service_info:
|
||||
port = int(service_info["ports"][0])
|
||||
if opts.o.debug:
|
||||
print(f"service port: {port}")
|
||||
service = client.V1Service(
|
||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-nodeport"),
|
||||
spec=client.V1ServiceSpec(
|
||||
type="NodePort",
|
||||
ports=[client.V1ServicePort(
|
||||
port=port,
|
||||
target_port=port
|
||||
)],
|
||||
selector={"app": self.app_name}
|
||||
)
|
||||
)
|
||||
return service
|
||||
|
||||
def get_ingress(self, use_tls=False):
|
||||
# No ingress for a deployment that has no http-proxy defined, for now
|
||||
http_proxy_info_list = self.spec.get_http_proxy()
|
||||
ingress = None
|
||||
@@ -93,7 +116,7 @@ class ClusterInfo:
|
||||
tls = [client.V1IngressTLS(
|
||||
hosts=[host_name],
|
||||
secret_name=f"{self.app_name}-tls"
|
||||
)]
|
||||
)] if use_tls else None
|
||||
paths = []
|
||||
for route in http_proxy_info["routes"]:
|
||||
path = route["path"]
|
||||
|
||||
@@ -20,6 +20,7 @@ from kubernetes import client, config
|
||||
from stack_orchestrator import constants
|
||||
from stack_orchestrator.deploy.deployer import Deployer, DeployerConfigGenerator
|
||||
from stack_orchestrator.deploy.k8s.helpers import create_cluster, destroy_cluster, load_images_into_kind
|
||||
from stack_orchestrator.deploy.k8s.helpers import install_ingress_for_kind, wait_for_ingress_in_kind
|
||||
from stack_orchestrator.deploy.k8s.helpers import pods_in_deployment, containers_in_pod, log_stream_from_string
|
||||
from stack_orchestrator.deploy.k8s.helpers import generate_kind_config
|
||||
from stack_orchestrator.deploy.k8s.cluster_info import ClusterInfo
|
||||
@@ -176,29 +177,47 @@ class K8sDeployer(Deployer):
|
||||
# Ensure the referenced containers are copied into kind
|
||||
load_images_into_kind(self.kind_cluster_name, self.cluster_info.image_set)
|
||||
self.connect_api()
|
||||
if self.is_kind():
|
||||
# Now configure an ingress controller (not installed by default in kind)
|
||||
install_ingress_for_kind()
|
||||
# Wait for ingress to start (deployment provisioning will fail unless this is done)
|
||||
wait_for_ingress_in_kind()
|
||||
|
||||
else:
|
||||
print("Dry run mode enabled, skipping k8s API connect")
|
||||
|
||||
self._create_volume_data()
|
||||
self._create_deployment()
|
||||
|
||||
if not self.is_kind():
|
||||
ingress: client.V1Ingress = self.cluster_info.get_ingress()
|
||||
# Note: at present we don't support tls for kind (and enabling tls causes errors)
|
||||
ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=not self.is_kind())
|
||||
if ingress:
|
||||
if opts.o.debug:
|
||||
print(f"Sending this ingress: {ingress}")
|
||||
if not opts.o.dry_run:
|
||||
ingress_resp = self.networking_api.create_namespaced_ingress(
|
||||
namespace=self.k8s_namespace,
|
||||
body=ingress
|
||||
)
|
||||
if opts.o.debug:
|
||||
print("Ingress created:")
|
||||
print(f"{ingress_resp}")
|
||||
else:
|
||||
if opts.o.debug:
|
||||
print("No ingress configured")
|
||||
|
||||
if ingress:
|
||||
nodeport: client.V1Service = self.cluster_info.get_nodeport()
|
||||
if nodeport:
|
||||
if opts.o.debug:
|
||||
print(f"Sending this nodeport: {nodeport}")
|
||||
if not opts.o.dry_run:
|
||||
nodeport_resp = self.core_api.create_namespaced_service(
|
||||
namespace=self.k8s_namespace,
|
||||
body=nodeport
|
||||
)
|
||||
if opts.o.debug:
|
||||
print(f"Sending this ingress: {ingress}")
|
||||
if not opts.o.dry_run:
|
||||
ingress_resp = self.networking_api.create_namespaced_ingress(
|
||||
namespace=self.k8s_namespace,
|
||||
body=ingress
|
||||
)
|
||||
if opts.o.debug:
|
||||
print("Ingress created:")
|
||||
print(f"{ingress_resp}")
|
||||
else:
|
||||
if opts.o.debug:
|
||||
print("No ingress configured")
|
||||
print("NodePort created:")
|
||||
print(f"{nodeport_resp}")
|
||||
|
||||
def down(self, timeout, volumes): # noqa: C901
|
||||
self.connect_api()
|
||||
@@ -269,20 +288,34 @@ class K8sDeployer(Deployer):
|
||||
except client.exceptions.ApiException as e:
|
||||
_check_delete_exception(e)
|
||||
|
||||
if not self.is_kind():
|
||||
ingress: client.V1Ingress = self.cluster_info.get_ingress()
|
||||
if ingress:
|
||||
if opts.o.debug:
|
||||
print(f"Deleting this ingress: {ingress}")
|
||||
try:
|
||||
self.networking_api.delete_namespaced_ingress(
|
||||
name=ingress.metadata.name, namespace=self.k8s_namespace
|
||||
)
|
||||
except client.exceptions.ApiException as e:
|
||||
_check_delete_exception(e)
|
||||
else:
|
||||
if opts.o.debug:
|
||||
print("No ingress to delete")
|
||||
ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=not self.is_kind())
|
||||
if ingress:
|
||||
if opts.o.debug:
|
||||
print(f"Deleting this ingress: {ingress}")
|
||||
try:
|
||||
self.networking_api.delete_namespaced_ingress(
|
||||
name=ingress.metadata.name, namespace=self.k8s_namespace
|
||||
)
|
||||
except client.exceptions.ApiException as e:
|
||||
_check_delete_exception(e)
|
||||
else:
|
||||
if opts.o.debug:
|
||||
print("No ingress to delete")
|
||||
|
||||
nodeport: client.V1Service = self.cluster_info.get_nodeport()
|
||||
if nodeport:
|
||||
if opts.o.debug:
|
||||
print(f"Deleting this nodeport: {ingress}")
|
||||
try:
|
||||
self.core_api.delete_namespaced_service(
|
||||
namespace=self.k8s_namespace,
|
||||
name=nodeport.metadata.name
|
||||
)
|
||||
except client.exceptions.ApiException as e:
|
||||
_check_delete_exception(e)
|
||||
else:
|
||||
if opts.o.debug:
|
||||
print("No nodeport to delete")
|
||||
|
||||
if self.is_kind():
|
||||
# Destroy the kind cluster
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
# 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/>.
|
||||
|
||||
from kubernetes import client
|
||||
from kubernetes import client, utils, watch
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import re
|
||||
from typing import Set, Mapping, List
|
||||
|
||||
from stack_orchestrator.util import get_k8s_dir, error_exit
|
||||
from stack_orchestrator.opts import opts
|
||||
from stack_orchestrator.deploy.deploy_util import parsed_pod_files_map_from_file_names
|
||||
from stack_orchestrator.deploy.deployer import DeployerException
|
||||
@@ -44,6 +45,33 @@ def destroy_cluster(name: str):
|
||||
_run_command(f"kind delete cluster --name {name}")
|
||||
|
||||
|
||||
def wait_for_ingress_in_kind():
|
||||
core_v1 = client.CoreV1Api()
|
||||
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",
|
||||
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")
|
||||
return
|
||||
print("Waiting for ingress controller to become ready...")
|
||||
warned_waiting = True
|
||||
error_exit("ERROR: Timed out waiting for ingress to become ready")
|
||||
|
||||
|
||||
def install_ingress_for_kind():
|
||||
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")
|
||||
utils.create_from_yaml(api_client, yaml_file=ingress_install)
|
||||
|
||||
|
||||
def load_images_into_kind(kind_cluster_name: str, image_set: Set[str]):
|
||||
for image in image_set:
|
||||
result = _run_command(f"kind load docker-image {image} --name {kind_cluster_name}")
|
||||
@@ -198,7 +226,8 @@ def _generate_kind_mounts(parsed_pod_files, deployment_dir, deployment_context):
|
||||
)
|
||||
|
||||
|
||||
def _generate_kind_port_mappings(parsed_pod_files):
|
||||
# TODO: decide if we need this functionality
|
||||
def _generate_kind_port_mappings_from_services(parsed_pod_files):
|
||||
port_definitions = []
|
||||
for pod in parsed_pod_files:
|
||||
parsed_pod_file = parsed_pod_files[pod]
|
||||
@@ -220,6 +249,19 @@ def _generate_kind_port_mappings(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")
|
||||
return (
|
||||
"" if len(port_definitions) == 0 else (
|
||||
" extraPortMappings:\n"
|
||||
f"{''.join(port_definitions)}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Note: this makes any duplicate definition in b overwrite a
|
||||
def merge_envs(a: Mapping[str, str], b: Mapping[str, str]) -> Mapping[str, str]:
|
||||
result = {**a, **b}
|
||||
@@ -284,6 +326,12 @@ def generate_kind_config(deployment_dir: Path, deployment_context):
|
||||
"apiVersion: kind.x-k8s.io/v1alpha4\n"
|
||||
"nodes:\n"
|
||||
"- role: control-plane\n"
|
||||
" kubeadmConfigPatches:\n"
|
||||
" - |\n"
|
||||
" kind: InitConfiguration\n"
|
||||
" nodeRegistration:\n"
|
||||
" kubeletExtraArgs:\n"
|
||||
" node-labels: \"ingress-ready=true\"\n"
|
||||
f"{port_mappings_yml}\n"
|
||||
f"{mounts_yml}\n"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user