# DNS, Ingress, Certificates
Install the basic tools, before running some applications like DNS integration (external-dns (opens new window)), Ingress (ingress-nginx (opens new window)), certificate management (cert-manager (opens new window)), ...
# cert-manager
Install cert-manager
helm chart (opens new window)
and modify the
default values (opens new window).
Service account external-dns
was created by eksctl
.
helm repo add --force-update jetstack https://charts.jetstack.io
helm upgrade --install --version v1.5.3 --namespace cert-manager --wait --values - cert-manager jetstack/cert-manager << EOF
installCRDs: true
serviceAccount:
create: false
name: cert-manager
extraArgs:
- --enable-certificate-owner-ref=true
prometheus:
servicemonitor:
enabled: true
webhook:
# Needed for Calico
securePort: 10251
hostNetwork: true
EOF
sleep 10
Add ClusterIssuers for Let's Encrypt staging and production:
kubectl apply -f - << EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging-dns
namespace: cert-manager
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: ${MY_EMAIL}
privateKeySecretRef:
name: letsencrypt-staging-dns
solvers:
- selector:
dnsZones:
- ${CLUSTER_FQDN}
dns01:
route53:
region: ${AWS_DEFAULT_REGION}
---
# Create ClusterIssuer for production to get real signed certificates
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production-dns
namespace: cert-manager
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ${MY_EMAIL}
privateKeySecretRef:
name: letsencrypt-production-dns
solvers:
- selector:
dnsZones:
- ${CLUSTER_FQDN}
dns01:
route53:
region: ${AWS_DEFAULT_REGION}
EOF
kubectl wait --namespace cert-manager --timeout=10m --for=condition=Ready clusterissuer --all
Create wildcard certificate using cert-manager
:
kubectl apply -f - << EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
namespace: cert-manager
spec:
secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
secretTemplate:
annotations:
kubed.appscode.com/sync: ""
issuerRef:
name: letsencrypt-${LETSENCRYPT_ENVIRONMENT}-dns
kind: ClusterIssuer
commonName: "*.${CLUSTER_FQDN}"
dnsNames:
- "*.${CLUSTER_FQDN}"
- "${CLUSTER_FQDN}"
EOF
kubectl wait --namespace cert-manager --for=condition=Ready --timeout=20m certificate "ingress-cert-${LETSENCRYPT_ENVIRONMENT}"
# kubed
kubed
- tool which helps with copying the certificate secretes across the
namespaces.
See the details:
- https://cert-manager.io/docs/faq/kubed/ (opens new window)
- https://web.archive.org/web/20230605162825/https://appscode.com/products/kubed/v0.12.0/guides/config-syncer/intra-cluster/ (opens new window)
Install kubed
helm chart (opens new window)
and modify the
default values (opens new window).
helm repo add --force-update appscode https://charts.appscode.com/stable/
helm upgrade --install --version v0.12.0 --namespace kubed --create-namespace --values - kubed appscode/kubed << EOF
imagePullPolicy: Always
config:
clusterName: ${CLUSTER_FQDN}
EOF
# ingress-nginx
Install ingress-nginx
helm chart (opens new window)
and modify the
default values (opens new window).
helm repo add --force-update ingress-nginx https://kubernetes.github.io/ingress-nginx
helm upgrade --install --version 3.35.0 --namespace ingress-nginx --create-namespace --wait --values - ingress-nginx ingress-nginx/ingress-nginx << EOF
controller:
# Needed for Calico
hostNetwork: true
replicaCount: 1
service:
annotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "$(echo "${TAGS}" | tr " " ,)"
metrics:
enabled: true
serviceMonitor:
enabled: true
prometheusRule:
enabled: true
rules:
- alert: NGINXConfigFailed
expr: count(nginx_ingress_controller_config_last_reload_successful == 0) > 0
for: 1s
labels:
severity: critical
annotations:
description: bad ingress config - nginx config test failed
summary: uninstall the latest ingress changes to allow config reloads to resume
- alert: NGINXCertificateExpiry
expr: (avg(nginx_ingress_controller_ssl_expire_time_seconds) by (host) - time()) < 604800
for: 1s
labels:
severity: critical
annotations:
description: ssl certificate(s) will expire in less then a week
summary: renew expiring certificates to avoid downtime
- alert: NGINXTooMany500s
expr: 100 * ( sum( nginx_ingress_controller_requests{status=~"5.+"} ) / sum(nginx_ingress_controller_requests) ) > 5
for: 1m
labels:
severity: warning
annotations:
description: Too many 5XXs
summary: More than 5% of all requests returned 5XX, this requires your attention
- alert: NGINXTooMany400s
expr: 100 * ( sum( nginx_ingress_controller_requests{status=~"4.+"} ) / sum(nginx_ingress_controller_requests) ) > 5
for: 1m
labels:
severity: warning
annotations:
description: Too many 4XXs
summary: More than 5% of all requests returned 4XX, this requires your attention
EOF
# Istio and related tools
# Jaeger
Install jaeger-operator
helm chart (opens new window)
and modify the
default values (opens new window).
helm repo add --force-update jaegertracing https://jaegertracing.github.io/helm-charts
helm upgrade --install --version 2.23.0 --namespace jaeger-operator --create-namespace --values - jaeger-operator jaegertracing/jaeger-operator << EOF
rbac:
clusterRole: true
EOF
Allow Jaeger to install Jaeger into jaeger-controlplane
:
kubectl get namespace jaeger-system &> /dev/null || kubectl create namespace jaeger-system
# https://github.com/jaegertracing/jaeger-operator/blob/master/deploy/cluster_role_binding.yaml
kubectl apply -f - << EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: jaeger-operator-in-jaeger-system
namespace: jaeger-system
subjects:
- kind: ServiceAccount
name: jaeger-operator
namespace: jaeger-operator
roleRef:
kind: Role
name: jaeger-operator
apiGroup: rbac.authorization.k8s.io
EOF
Create Jaeger using the operator:
kubectl apply -f - << EOF
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
namespace: jaeger-system
name: jaeger-controlplane
spec:
strategy: AllInOne
allInOne:
image: jaegertracing/all-in-one:1.21
options:
log-level: debug
storage:
type: memory
options:
memory:
max-traces: 100000
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/auth-url: https://oauth2-proxy.${CLUSTER_FQDN}/oauth2/auth
nginx.ingress.kubernetes.io/auth-signin: https://oauth2-proxy.${CLUSTER_FQDN}/oauth2/start?rd=\$scheme://\$host\$request_uri
hosts:
- jaeger.${CLUSTER_FQDN}
tls:
- secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
hosts:
- jaeger.${CLUSTER_FQDN}
EOF
Allow Jaeger to be monitored by Prometheus https://github.com/jaegertracing/jaeger-operator/issues/538 (opens new window):
kubectl apply -f - << EOF
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: tracing
namespace: jaeger-system
spec:
podMetricsEndpoints:
- interval: 5s
port: "admin-http"
selector:
matchLabels:
app: jaeger
EOF
# Istio
Download istioctl
:
ISTIO_VERSION="1.10.2"
if ! command -v istioctl &> /dev/null; then
if [[ $(uname) == "Darwin" ]]; then
ISTIOCTL_URL="https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istioctl-${ISTIO_VERSION}-osx.tar.gz"
else
ISTIOCTL_URL="https://github.com/istio/istio/releases/download/${ISTIO_VERSION}/istioctl-${ISTIO_VERSION}-linux-amd64.tar.gz"
fi
curl -s -L ${ISTIOCTL_URL} | sudo tar xz -C /usr/local/bin/
fi
Clone the istio
repository and install istio-operator
helm chart (opens new window)
and modify the
default values (opens new window).
test -d "tmp/${CLUSTER_FQDN}/istio" || git clone --quiet https://github.com/istio/istio.git "tmp/${CLUSTER_FQDN}/istio"
git -C "tmp/${CLUSTER_FQDN}/istio" checkout --quiet "${ISTIO_VERSION}"
helm upgrade --install istio-operator "tmp/${CLUSTER_FQDN}/istio/manifests/charts/istio-operator"
Create Istio using the operator:
kubectl get namespace istio-system &> /dev/null || kubectl create namespace istio-system
kubectl apply -f - << EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
namespace: istio-system
name: istio-controlplane
spec:
profile: default
meshConfig:
enableTracing: true
enableAutoMtls: true
defaultConfig:
tracing:
zipkin:
address: "jaeger-controlplane-collector-headless.jaeger-system.svc.cluster.local:9411"
sampling: 100
sds:
enabled: true
components:
egressGateways:
- name: istio-egressgateway
enabled: true
ingressGateways:
- name: istio-ingressgateway
enabled: true
k8s:
serviceAnnotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "${TAGS// /,}"
pilot:
k8s:
overlays:
- kind: Deployment
name: istiod
patches:
- path: spec.template.spec.hostNetwork
value: true
# Reduce resource requirements for local testing. This is NOT recommended for the real use cases
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 100m
memory: 64Mi
EOF
Enable Prometheus monitoring:
kubectl apply -f "https://raw.githubusercontent.com/istio/istio/${ISTIO_VERSION}/samples/addons/extras/prometheus-operator.yaml"
# Kiali
Install kiali-operator
helm chart (opens new window)
and modify the
default values (opens new window).
helm repo add --force-update kiali https://kiali.org/helm-charts
helm upgrade --install --version 1.38.1 --namespace kiali-operator --create-namespace kiali-operator kiali/kiali-operator
Install Kiali CR:
# https://github.com/kiali/kiali-operator/blob/master/deploy/kiali/kiali_cr.yaml
kubectl get namespace kiali &> /dev/null || kubectl create namespace kiali
kubectl get secret -n kiali kiali || kubectl create secret generic kiali --from-literal="oidc-secret=${MY_PASSWORD}" -n kiali
kubectl apply -f - << EOF
apiVersion: kiali.io/v1alpha1
kind: Kiali
metadata:
namespace: kiali-operator
name: kiali
spec:
istio_namespace: istio-system
auth:
strategy: openid
openid:
client_id: kiali.${CLUSTER_FQDN}
disable_rbac: true
insecure_skip_verify_tls: true
issuer_uri: "https://dex.${CLUSTER_FQDN}"
username_claim: email
deployment:
accessible_namespaces: ["**"]
namespace: kiali
override_ingress_yaml:
spec:
rules:
- host: kiali.${CLUSTER_FQDN}
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: kiali
port:
number: 20001
tls:
- secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
hosts:
- kiali.${CLUSTER_FQDN}
external_services:
grafana:
is_core_component: true
url: "https://grafana.${CLUSTER_FQDN}"
in_cluster_url: "http://kube-prometheus-stack-grafana.kube-prometheus-stack.svc.cluster.local:80"
prometheus:
is_core_component: true
url: http://kube-prometheus-stack-prometheus.kube-prometheus-stack.svc.cluster.local:9090
tracing:
is_core_component: true
url: https://jaeger.${CLUSTER_FQDN}
in_cluster_url: http://jaeger-controlplane-query.jaeger-system.svc.cluster.local:16686
server:
web_fqdn: kiali.${CLUSTER_FQDN}
web_root: /
EOF
# external-dns
Install external-dns
helm chart (opens new window)
and modify the
default values (opens new window).
external-dns
will take care about DNS records.
Service account external-dns
was created by eksctl
.
helm repo add --force-update bitnami https://charts.bitnami.com/bitnami
helm upgrade --install --version 5.4.1 --namespace external-dns --values - external-dns bitnami/external-dns << EOF
sources:
- ingress
- istio-gateway
- istio-virtualservice
- service
aws:
region: ${AWS_DEFAULT_REGION}
domainFilters:
- ${CLUSTER_FQDN}
interval: 20s
policy: sync
serviceAccount:
create: false
name: external-dns
metrics:
enabled: true
serviceMonitor:
enabled: true
EOF
# mailhog
Install mailhog
helm chart (opens new window)
and modify the
default values (opens new window).
helm repo add --force-update codecentric https://codecentric.github.io/helm-charts
helm upgrade --install --version 4.1.0 --namespace mailhog --create-namespace --values - mailhog codecentric/mailhog << EOF
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/auth-url: https://oauth2-proxy.${CLUSTER_FQDN}/oauth2/auth
nginx.ingress.kubernetes.io/auth-signin: https://oauth2-proxy.${CLUSTER_FQDN}/oauth2/start?rd=\$scheme://\$host\$request_uri
hosts:
- host: mailhog.${CLUSTER_FQDN}
paths: ["/"]
tls:
- secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
hosts:
- mailhog.${CLUSTER_FQDN}
EOF
# kubewatch - obsolete
Install kubewatch
:
# helm upgrade --install --version 3.2.13 --namespace kubewatch --create-namespace --values - kubewatch bitnami/kubewatch << EOF
# slack:
# enabled: true
# channel: "#${SLACK_CHANNEL}"
# token: ${SLACK_BOT_API_TOKEN}
# smtp:
# enabled: true
# to: "notification@${CLUSTER_FQDN}"
# from: "kubewatch@${CLUSTER_FQDN}"
# smarthost: "mailhog.mailhog.svc.cluster.local:1025"
# subject: "kubewatch"
# requireTLS: false
# resourcesToWatch:
# deployment: false
# pod: false
# persistentvolume: true
# namespace: true
# rbac:
# create: false
# EOF
Create ClusterRole
and ClusterRoleBinding
to allow kubewatch
to access
necessary resources:
# kubectl apply -f - << EOF
# apiVersion: rbac.authorization.k8s.io/v1beta1
# kind: ClusterRole
# metadata:
# name: system:kubewatch
# rules:
# - apiGroups:
# - ""
# resources:
# - namespaces
# - persistentvolumes
# verbs:
# - list
# - watch
# - get
# ---
# apiVersion: rbac.authorization.k8s.io/v1beta1
# kind: ClusterRoleBinding
# metadata:
# name: system:kubewatch
# roleRef:
# apiGroup: rbac.authorization.k8s.io
# kind: ClusterRole
# name: system:kubewatch
# subjects:
# - kind: ServiceAccount
# name: kubewatch
# namespace: kubewatch
# EOF
# Calico commands
calicoctl --allow-version-mismatch ipam show --show-block
Output:
+----------+-------------------+-----------+------------+--------------+
| GROUPING | CIDR | IPS TOTAL | IPS IN USE | IPS FREE |
+----------+-------------------+-----------+------------+--------------+
| IP Pool | 172.16.0.0/16 | 65536 | 42 (0%) | 65494 (100%) |
| Block | 172.16.166.128/26 | 64 | 14 (22%) | 50 (78%) |
| Block | 172.16.2.128/26 | 64 | 13 (20%) | 51 (80%) |
| Block | 172.16.3.64/26 | 64 | 15 (23%) | 49 (77%) |
+----------+-------------------+-----------+------------+--------------+
Block outgoing traffic form calico-test-1
namespace:
kubectl get namespace calico-test-1 &> /dev/null || kubectl create namespace calico-test-1
calicoctl --allow-version-mismatch apply -f - << EOF
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: calico-test-disable-all-egress
spec:
namespaceSelector: has(projectcalico.org/name) && projectcalico.org/name starts with "calico-test"
types:
- Ingress
- Egress
ingress:
- action: Allow
egress:
# Except DNS
- action: Allow
protocol: TCP
destination:
ports:
- 53
- action: Allow
protocol: UDP
destination:
ports:
- 53
EOF
kubectl run curl-test-1 --timeout=10m --namespace calico-test-1 --image=radial/busyboxplus:curl --rm -it -- ping -c 3 -w 50 www.google.com
Output:
--- www.google.com ping statistics ---
50 packets transmitted, 0 packets received, 100% packet loss
Try the same in calico-test-2
, but allow traffic to 1.1.1.1
:
kubectl get namespace calico-test-2 &> /dev/null || kubectl create namespace calico-test-2
kubectl apply -f - << EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-1.1.1.1
namespace: calico-test-2
spec:
podSelector: {}
egress:
- to:
- ipBlock:
cidr: 1.1.1.1/32
EOF
kubectl run curl-test-1 --namespace calico-test-2 --image=radial/busyboxplus:curl --rm -it -- ping -c 3 -w 50 www.google.com
kubectl run curl-test-2 --namespace calico-test-2 --image=radial/busyboxplus:curl --rm -it -- ping -c 3 -w 50 1.1.1.1
Output:
--- www.google.com ping statistics ---
50 packets transmitted, 0 packets received, 100% packet loss
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.309/1.348/1.399 ms
Ping should be working fine in namespaces which doesn't start with calico-test
(like default
):
kubectl run curl-test --namespace default --image=radial/busyboxplus:curl --rm -it -- ping -c 3 -w 50 www.google.com
Output:
--- www.google.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.398/1.417/1.430 ms