Testing with Kubernetes Locally¶
We will use an official tool Kubernetes-In-Docker (KIND) to run Kubernetes via Docker locally.
This is useful for testing Kubernetes configuration locally, and trialing software too.
Install Tools¶
Kubectl¶
Used to control Kubernetes clusters.
temp_dir=$(mktemp -d)
cd "${temp_dir}"
[ $(uname -m) = x86_64 ] && curl -Lo ./kubectl \
https://dl.k8s.io/release/\
$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/\
$(uname -s | tr '[:upper:]' '[:lower:]')/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
rm -rf "$temp_dir"
KIND¶
Used to run a Kubernetes cluster locally.
temp_dir=$(mktemp -d)
cd "${temp_dir}"
[ $(uname -m) = x86_64 ] && curl -Lo ./kind \
https://kind.sigs.k8s.io/dl/v0.22.0/kind-$(uname)-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
rm -rf "$temp_dir"
Optional Config
Kubie: used to easily switch Kubernetes context (i.e. multiple clusters).temp_dir=$(mktemp -d)
cd "${temp_dir}"
[ $(uname -m) = x86_64 ] && curl -Lo ./kubie \
https://github.com/sbstp/kubie/releases/download/v0.23.0/kubie-\
$(uname -s | tr '[:upper:]' '[:lower:]')-amd64
chmod +x ./kubie
sudo mv ./kubie /usr/local/bin/kubie
rm -rf "$temp_dir"
temp_dir=$(mktemp -d)
cd "${temp_dir}"
[ $(uname -m) = x86_64 ] && curl -Lo ./helm.tar.gz \
https://get.helm.sh/helm-v3.14.3-$(uname -s | tr '[:upper:]' '[:lower:]')-amd64.tar.gz
tar -xvzf helm.tar.gz
sudo mv $(uname -s | tr '[:upper:]' '[:lower:]')-amd64/helm /usr/local/bin/helm
rm -rf "$temp_dir"
echo alias k='kubectl' >> ~/.bashrc
echo alias kcc='kubie ctx' >> ~/.bashrc
echo alias ns='kubie ns' >> ~/.bashrc
source ~/.bashrc
echo alias k='kubectl' >> ~/.config/fish/config.fish
echo alias kcc='kubie ctx' >> ~/.config/fish/config.fish
echo alias ns='kubie ns' >> ~/.config/fish/config.fish
source ~/.config/fish/config.fish
Create a Cluster¶
- Run the following to create a cluster with Ingress ports bound:
cat <<EOF | kind create cluster --name local --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 7080
protocol: TCP
- containerPort: 443
hostPort: 7433
protocol: TCP
EOF
The cluster will be named 'kind-local'
Fish shell equivalent
kind create cluster --name local --config (echo '
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 7080
protocol: TCP
- containerPort: 443
hostPort: 7433
protocol: TCP
' | psub)
Cluster services will be accessible under http://localhost:7080
Change the hostPort variable if you wish to use a different port.
Deploy the Ingress Controller¶
Contour uses Envoy and may be a good choice in production.
But the simplest and most battle tested in the Nginx ingress.
Deploy with:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
Check when it is ready:
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=90s
Test the Ingress¶
Run test service:
cat <<EOF | kubectl apply --filename=-
kind: Pod
apiVersion: v1
metadata:
name: foo-app
labels:
app: foo
spec:
containers:
- command:
- /agnhost
- netexec
- --http-port
- "8080"
image: registry.k8s.io/e2e-test-images/agnhost:2.39
name: foo-app
---
kind: Service
apiVersion: v1
metadata:
name: foo-service
spec:
selector:
app: foo
ports:
# Default port used by the image
- port: 8080
---
kind: Pod
apiVersion: v1
metadata:
name: bar-app
labels:
app: bar
spec:
containers:
- command:
- /agnhost
- netexec
- --http-port
- "8080"
image: registry.k8s.io/e2e-test-images/agnhost:2.39
name: bar-app
---
kind: Service
apiVersion: v1
metadata:
name: bar-service
spec:
selector:
app: bar
ports:
# Default port used by the image
- port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- http:
paths:
- pathType: Prefix
path: /foo(/|$)(.*)
backend:
service:
name: foo-service
port:
number: 8080
- pathType: Prefix
path: /bar(/|$)(.*)
backend:
service:
name: bar-service
port:
number: 8080
---
EOF
Fish shell equivalent
kubectl apply --filename (echo '
kind: Pod
apiVersion: v1
metadata:
name: foo-app
labels:
app: foo
spec:
containers:
- command:
- /agnhost
- netexec
- --http-port
- "8080"
image: registry.k8s.io/e2e-test-images/agnhost:2.39
name: foo-app
---
kind: Service
apiVersion: v1
metadata:
name: foo-service
spec:
selector:
app: foo
ports:
# Default port used by the image
- port: 8080
---
kind: Pod
apiVersion: v1
metadata:
name: bar-app
labels:
app: bar
spec:
containers:
- command:
- /agnhost
- netexec
- --http-port
- "8080"
image: registry.k8s.io/e2e-test-images/agnhost:2.39
name: bar-app
---
kind: Service
apiVersion: v1
metadata:
name: bar-service
spec:
selector:
app: bar
ports:
# Default port used by the image
- port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- http:
paths:
- pathType: Prefix
path: /foo(/|$)(.*)
backend:
service:
name: foo-service
port:
number: 8080
- pathType: Prefix
path: /bar(/|$)(.*)
backend:
service:
name: bar-service
port:
number: 8080
---
' | psub)
$ curl localhost:7080/foo/hostname
should output "foo-app"
$ curl localhost:7080/bar/hostname
should output "bar-app"
Cleanup test resources:
kubectl delete pod foo-app
kubectl delete pod bar-app
kubectl delete svc foo-service
kubectl delete svc bar-service
kubectl delete ingress example-ingress