| summary |
|---|
Kubenetes services with external fixed ips using keepalived |
volume: A target for data storage - can be anything e.g. NFS, local disk, etc.service: Describes what ports are reachible how on which deployment / containerdeployment: Describes a set ofcontainersthat are deployed on a single host - commonly represented by a pod in kubernetespod: "namespace forcontainers" - similar to a docker-compose stacknode: physical / vm machine with a set ofpodscontainer: Similar to a docker container
...good for first trys. It installs kubectl and kubeadm with a single node inside a "vm".
-> https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get updatesudo apt-get install -y kubelet kubeadm kubectl docker.io
sudo apt-mark hold kubelet kubeadm kubectl # Freeze packages to prevent accidantial updates-> https://phoenixnap.com/kb/how-to-install-kubernetes-on-a-bare-metal-server
sudo bash
cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOFsudo kubeadm init --pod-network-cidr=10.111.0.0/16 --apiserver-advertise-address [CLUSTER_MASTER_IP]You must then run these commands to retrive the kubeconfig file and token.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/configUsing Flannel as network addon (no sudo anymore!) - more information is available here:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.ymlChanged the configmap kube-flannel-cfg inside namespace kube-system and set net-conf.json to:
{
"Network": "10.111.0.0/16",
"EnableIPv6": false,
"Backend": {
"Type": "vxlan",
"GBP": true,
"DirectRouting": true
}
}
It would be interesting to use other backends - like Wireguard at some point. This is especially needed in distributed setups.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.1/components.yamlYou see this error in the logs? kubernetes-sigs/metrics-server#196 Solve it by introducing certificates into the cluster:
export EDITOR=nano
kubectl edit configmap/kubelet-config-1.23 -n kube-systemMake sure to run from time to time:
sudo kubeadm certs check-expiration
sudo kubeadm certs renew allsudo kubeadm join [CLUSTER_MASTER_IP]:6443 --token 2ze2pr.a0z1IhfA3kdacqd7 --discovery-token-ca-cert-hash sha256:62e03606c3e4fee86bd016915283e8761a30a5b6b93219faad7547c5eca71c29You can get the command again using this: https://stackoverflow.com/a/71137163/11664229
This is needed if you plan to connect to your masters using e.g. their CNAMEs instead of their IP addresses (required by the Lens IDE).
-> https://blog.scottlowe.org/2019/07/30/adding-a-name-to-kubernetes-api-server-certificate/
-> https://helm.sh/docs/intro/install/#from-apt-debianubuntu
The problem is described there: https://itnext.io/keep-you-kubernetes-cluster-balanced-the-secret-to-high-availability-17edf60d9cb7
Add it to your cluster:
helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm install descheduler --namespace kube-system descheduler/deschedulerNow upgrade the release as you wish using the values.yml.
This is useful for automatic updates of pods (and their container images). -> https://keel.sh
If you really want it... There you go:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml
kubectl proxy --address='0.0.0.0' --accept-hosts='.*'Add to new file dashboard-adminuser.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboardkubectl apply -f dashboard-adminuser.yaml
kubectl create clusterrolebinding kubernetes-dashboard --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:kubernetes-dashboardkubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
# OR
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep kubernetes-dashboard | cut -d " " -f1)Does the Deployment needs to be reachible on any port?
-> Yes. Proceed.
-> No? Done.
Does the Deployment needs to be externally reachible on HTTP/S?
-> Yes: Create a service and configure your ingress to use it (or instruct NGINX to proxy). Proceed.
-> No. Proceed.
Does the Deployment needs to be reachible on an other port by the cluster?
-> Yes: Create a service and configure your pods to use its DNS name. Proceed.
-> No. Proceed.
Does the Deployment needs to be reachible on an other port by external clients?
-> Yes: Create a service (e.g. LoadBalancer) with a NodePort. Proceed.
-> No. Proceed.
Does the Deployment needs to be reachible on an other port by external clients with the original clients ip visible?
-> Yes: Adapt the following YAML to your needs. Keep in mind to correctly configure the ip / interfaces (or skip keepalived if using a real external load-balancer). Done.
-> No. Done.
This is the example configuration YAML-file. It will spin up a NGINX instance, which just replies the clients IP to test the transparency.
Keep in mind to change the external service ip (192.168.0.100) and incoming interface it should be assigned to (enp6s0), as well as the external port (8080).
apiVersion: v1
kind: Namespace
metadata:
name: service-with-external-ip
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: external-service-ip
namespace: service-with-external-ip
spec:
selector:
matchLabels:
name: external-service-ip
template:
metadata:
labels:
name: external-service-ip
spec:
hostNetwork: true
containers:
- name: keepalived
image: arcts/keepalived
securityContext:
capabilities:
add: ["NET_ADMIN"]
env:
- name: KEEPALIVED_INTERFACE
value: "enp6s0"
- name: KEEPALIVED_VIRTUAL_IPADDRESS_1
value: "192.168.0.100/24 dev enp6s0"
- name: KEEPALIVED_VIRTUAL_ROUTER_ID
value: "100"
---
apiVersion: v1
kind: Service
metadata:
name: external-service
namespace: service-with-external-ip
spec:
type: LoadBalancer
selector:
app: backend
ports:
- name: demo
protocol: TCP
port: 8080
targetPort: 80
externalIPs:
- 192.168.0.100
---
apiVersion: v1
kind: ConfigMap
metadata:
name: backend-config
namespace: service-with-external-ip
data:
default.conf: |
server {
listen 80 default_server;
server_name _;
location / {
add_header Content-Type text/html;
return 200 $remote_addr;
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: service-with-external-ip
spec:
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: echo-ip
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/conf.d/default.conf
name: backend-config-volume
subPath: default.conf
volumes:
- name: backend-config-volume
configMap:
name: backend-config...maybe this will work with externalTrafficPolicy: Local, as at some point Kubernetes will may support requiredDuringSchedulingRequiredDuringExecution.
This would allow keepalived to only run on pods on which the deployment is executed. An example affinity would look like this:
affinity:
podAffinity:
requiredDuringSchedulingRequiredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nextcloud-media
topologyKey: kubernetes.io/hostnameCurrently with requiredDuringSchedulingIgnoredDuringExecution this won't work, as the keepalived instance is not moved in case the deployment gets evicted. If you really need this take a look into the descheduler project...
In case you need a config map, which describes a whole folder...
import os, re, sys, subprocess
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--volume', type=str, required=True, help='Name of volume and target YAML path')
parser.add_argument('--dir', type=str, required=True, help='Which path sould be serialized?')
parser.add_argument('--mountPath', type=str, required=True, help='Where should the serialized data go?')
args = parser.parse_args()
def makeSimplePath(p):
return re.sub(r'-+', '-', re.sub(r'[^a-z0-9]', '-', p.lower()))
dirs = [args.dir]
for p,d,f in os.walk(args.dir):
for dd in d:
dirs.append(os.path.join(p, dd))
maps = []
for dd in dirs:
print('Serializing path: ' + dd)
maps.append([dd, subprocess.check_output(['kubectl', 'create', 'configmap', args.volume + '-files-' + makeSimplePath(dd), '--from-file=' + dd, '-o', 'yaml', '--dry-run=client'], ).decode()])
with open(args.volume + '.yml', 'w') as f:
f.write(f'# This file was generated using: {" ".join(sys.argv)}\n')
f.write('---\n')
for m in maps:
f.write(m[1])
f.write('---\n')
print('Now append this to your YAML...')
print('volumeMounts:')
print(f'# Warning: All volumes beginning with "{args.volume}-files-" are generated automatically. Do not touch them!')
for m in maps:
print('- name: ' + args.volume + '-files-' + makeSimplePath(m[0]) + '-volume')
print(' mountPath: ' + os.path.join(args.mountPath, os.path.relpath(m[0], args.dir)))
print(' readOnly: true')
print()
print('volumes:')
print(f'# Warning: All volumes beginning with "{args.volume}-files-" are generated automatically. Do not touch them!')
for m in maps:
print('- name: ' + args.volume + '-files-' + makeSimplePath(m[0]) + '-volume')
print(' configMap:')
print(' name: ' + args.volume + '-files-' + makeSimplePath(m[0]))