Skip to content

Instantly share code, notes, and snippets.

@sonalkr132
Last active March 15, 2022 00:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sonalkr132/bd5c81ff48145b3e5a930ffe895deda1 to your computer and use it in GitHub Desktop.
Save sonalkr132/bd5c81ff48145b3e5a930ffe895deda1 to your computer and use it in GitHub Desktop.
medium blog code snippets

Networking setup

# create the vpc for our k8s cluster
gcloud compute networks create k8s-vpc --subnet-mode custom

# add a subnet with 10.240.0.0/24 CIDR in us-west1
gcloud compute networks subnets create k8s-west \
    --network k8s-vpc \
    --range 10.240.0.0/24 \
    --region us-west1

# add a firewall rule to allow all traffic between compute nodes
gcloud compute firewall-rules create k8s-allow-internal \
  --allow tcp,udp,icmp \
  --network k8s-vpc \
  --source-ranges 10.240.0.0/24

# `gcloud compute ssh <node-name>` is used for shell access to private nodes
# create a firewall rule to whitelist GCP IAP range: 35.235.240.0/20
gcloud compute firewall-rules create k8s-allow-ssh \
    --network k8s-vpc \
    --source-ranges 35.235.240.0/20 \
    --allow tcp:22

Setup Cloud NAT

# Cloud NAT requires a cloud router
gcloud compute routers create k8s-router \
    --network k8s-vpc \
    --region us-west1

# create a NAT gateway for the us-west1 region
# note: you can optionally reserve a static IP for your NAT
gcloud compute routers nats create k8s-nat \
    --router-region us-west1 \
    --router k8s-router \
    --nat-all-subnet-ip-ranges \
    --auto-allocate-nat-external-ips

Provision compute instances

# create master nodes
for i in 0 1 2; do
  gcloud compute instances create master-${i} \
    --async \
    --boot-disk-size 100GB \
    --can-ip-forward \
    --image-family ubuntu-2004-lts \
    --image-project ubuntu-os-cloud \
    --machine-type e2-medium \
    --private-network-ip 10.240.0.1${i} \
    --scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \
    --subnet k8s-west \
    --tags controller,master \
    --no-address
done

# create at least one worker node
gcloud compute instances create worker-0 \
  --async \
  --boot-disk-size 100GB \
  --can-ip-forward \
  --image-family ubuntu-2004-lts \
  --image-project ubuntu-os-cloud \
  --machine-type e2-medium \
  --private-network-ip 10.240.0.20 \
  --scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \
  --subnet k8s-west \
  --tags worker \
  --no-address

Provision Load Balancer for k8s master

# add firewall rules to allow healthcheck from GCP load balancer
# source: https://cloud.google.com/load-balancing/docs/health-checks#firewall_rules
gcloud compute firewall-rules create fw-allow-network-lb-health-checks \
    --network=k8s-vpc \
    --action=ALLOW \
    --direction=INGRESS \
    --source-ranges=35.191.0.0/16,209.85.152.0/22,209.85.204.0/22,130.211.0.0/22 \
    --target-tags=allow-network-lb-health-checks \
    --rules=tcp

# create an unmanaged instance group and add a master node to it
gcloud compute instance-groups unmanaged create k8s-master \
    --zone=us-west1-c

gcloud compute instance-groups unmanaged add-instances k8s-master \
    --zone=us-west1-c \
    --instances=master-0

# create health check for k8s master. note, we have logging of healthecheck enabled.
# use resource.type="gce_instance_group" filter to see healthcheck logs
# you can disable healtcheck logging with: health-checks update https <name> --no-enable-logging
gcloud compute health-checks create https k8s-controller-hc --check-interval=5 \
    --enable-logging \
    --request-path=/healthz \
    --port=6443 \
    --region=us-west1

# crate a backend service
gcloud compute backend-services create k8s-service \
    --protocol TCP \
    --health-checks k8s-controller-hc \
    --health-checks-region us-west1 \
    --region us-west1

# add the instance group to your backend service
gcloud compute backend-services add-backend k8s-service \
    --instance-group k8s-master \
    --instance-group-zone us-west1-c \
    --region us-west1

# reserve static IP for network lb
gcloud compute addresses create k8s-lb --region us-west1

# create the load balancer which used the backend service and the static IP
gcloud compute forwarding-rules create k8s-forwarding-rule \
    --load-balancing-scheme external \
    --region us-west1 \
    --ports 6443 \
    --address k8s-lb \
    --backend-service k8s-service

Configure modules and sysctl params

# ssh to master and worker nodes
gcloud compute ssh master-0 --zone us-west1-c
gcloud compute ssh master-1 --zone us-west1-c
gcloud compute ssh master-2 --zone us-west1-c
gcloud compute ssh worker-0 --zone us-west1-c

# make sure swap is disabled. no output means disabled
sudo swapon --show

# ensure iptables see bridge traffic for masquerading
sudo modprobe overlay
sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward=1
EOF

# apply system params added above without reboot
# it will complain with following, you can safely ignore it
# sysctl: setting key "net.ipv4.conf.all.promote_secondaries": Invalid argument
# https://github.com/kubernetes/website/issues/26503
sudo sysctl --system

Install kubeadm, kubectl and kubelet

# add GCP apt repos and install
sudo apt-get update && sudo apt-get install -y apt-transport-https
wget -qO - https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update

# you can use kubelet=1.20.6-00 kubectl=1.20.6-00 kubeadm=1.20.6-00 if you need a specific version
sudo apt-get install -y kubelet kubectl kubeadm
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable kubelet

Install containerd

# set version you want to use
export VERSION=1.5.0
wget https://github.com/containerd/containerd/releases/download/v${VERSION}/cri-containerd-cni-${VERSION}-linux-amd64.tar.gz -O /tmp/cri-containerd-cni-${VERSION}-linux-amd64.tar.gz
sudo tar --no-overwrite-dir -C / -xzf /tmp/cri-containerd-cni-${VERSION}-linux-amd64.tar.gz
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# configure containerd to use systemd cgroup driver
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl daemon-reload
sudo systemctl start containerd
sudo systemctl enable containerd

Configure kubelet to run in stand-alone mode on master nodes

cat <<EOF | sudo tee /etc/systemd/system/kubelet.service.d/20-etcd-service-manager.conf
[Service]
ExecStart=
ExecStart=/usr/bin/kubelet --address=127.0.0.1 \
    --pod-manifest-path=/etc/kubernetes/manifests \
    --cgroup-driver=systemd \
    --container-runtime=remote \
    --container-runtime-endpoint=/run/containerd/containerd.sock
Restart=always
EOF

sudo systemctl daemon-reload
sudo systemctl restart kubelet

One time config for etcd cluster. Run following only on master-0

export HOST0=10.240.0.10
export HOST1=10.240.0.11
export HOST2=10.240.0.12

mkdir -p /tmp/${HOST0}/ /tmp/${HOST1}/ /tmp/${HOST2}/

ETCDHOSTS=(${HOST0} ${HOST1} ${HOST2})
NAMES=("infra0" "infra1" "infra2")

# config used by kubeadm to bootstrap the cluster
for i in "${!ETCDHOSTS[@]}"; do
HOST=${ETCDHOSTS[$i]}
NAME=${NAMES[$i]}
cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml
apiVersion: "kubeadm.k8s.io/v1beta2"
kind: ClusterConfiguration
etcd:
    local:
        serverCertSANs:
        - "${HOST}"
        peerCertSANs:
        - "${HOST}"
        extraArgs:
            initial-cluster: ${NAMES[0]}=https://${ETCDHOSTS[0]}:2380,${NAMES[1]}=https://${ETCDHOSTS[1]}:2380,${NAMES[2]}=https://${ETCDHOSTS[2]}:2380
            initial-cluster-state: new
            name: ${NAME}
            listen-peer-urls: https://${HOST}:2380
            listen-client-urls: https://${HOST}:2379
            advertise-client-urls: https://${HOST}:2379
            initial-advertise-peer-urls: https://${HOST}:2380
EOF
done

# create etcd ca and peer certs
sudo kubeadm init phase certs etcd-ca

sudo kubeadm init phase certs etcd-server --config=/tmp/${HOST2}/kubeadmcfg.yaml
sudo kubeadm init phase certs etcd-peer --config=/tmp/${HOST2}/kubeadmcfg.yaml
sudo kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST2}/kubeadmcfg.yaml
sudo kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST2}/kubeadmcfg.yaml
sudo cp -R /etc/kubernetes/pki /tmp/${HOST2}/

sudo find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete

sudo kubeadm init phase certs etcd-server --config=/tmp/${HOST1}/kubeadmcfg.yaml
sudo kubeadm init phase certs etcd-peer --config=/tmp/${HOST1}/kubeadmcfg.yaml
sudo kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST1}/kubeadmcfg.yaml
sudo kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST1}/kubeadmcfg.yaml
sudo cp -R /etc/kubernetes/pki /tmp/${HOST1}/
sudo find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete

sudo kubeadm init phase certs etcd-server --config=/tmp/${HOST0}/kubeadmcfg.yaml
sudo kubeadm init phase certs etcd-peer --config=/tmp/${HOST0}/kubeadmcfg.yaml
sudo kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST0}/kubeadmcfg.yaml
sudo kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST0}/kubeadmcfg.yaml

sudo find /tmp/${HOST2} -name ca.key -type f -delete
sudo find /tmp/${HOST1} -name ca.key -type f -delete

# start simple python server on master-0 to copy files to other nodes
sudo python3 -m http.server --directory /tmp/

Copy etcd certs to master-1 and master-2

# copy kubeadm config and etcd cert from master-0 to master-1
# run following on master-1 only
wget --no-parent -r http://10.240.0.10:8000/10.240.0.11/
sudo chown root:root -R ~/10.240.0.10\:8000/10.240.0.11/pki/
sudo mv ~/10.240.0.10\:8000/10.240.0.11/pki/ /etc/kubernetes/

# copy kubeadm config and etcd cert from master-0 to master-2
# run following on master-2 only
wget --no-parent -r http://10.240.0.10:8000/10.240.0.12/
sudo chown root:root -R ~/10.240.0.10\:8000/10.240.0.12/pki/
sudo mv ~/10.240.0.10\:8000/10.240.0.12/pki/ /etc/kubernetes/

Start etcd cluster

# run following command on master-0 only
sudo kubeadm init phase etcd local --config=/tmp/${HOST0}/kubeadmcfg.yaml
# run following command on master-1 only
sudo kubeadm init phase etcd local --config=10.240.0.10\:8000/10.240.0.11/kubeadmcfg.yaml
# run following command on master-2 only
sudo kubeadm init phase etcd local --config=10.240.0.10\:8000/10.240.0.12/kubeadmcfg.yaml

# check if etcd cluster is up
sudo crictl exec -it $(sudo crictl ps | grep etcd| cut -d " " -f1) etcdctl \
    --cert /etc/kubernetes/pki/etcd/peer.crt \
    --key /etc/kubernetes/pki/etcd/peer.key \
    --cacert /etc/kubernetes/pki/etcd/ca.crt \
    --endpoints https://10.240.0.10:2379 \
    endpoint health --write-out table --cluster
+--------------------------+--------+-------------+-------+
|         ENDPOINT         | HEALTH |    TOOK     | ERROR |
+--------------------------+--------+-------------+-------+
| https://10.240.0.10:2379 |   true | 17.834245ms |       |
| https://10.240.0.12:2379 |   true |  20.39666ms |       |
| https://10.240.0.11:2379 |   true | 22.587522ms |       |
+--------------------------+--------+-------------+-------+

Create kubeadm and GCP cloud config

# remove standalone kubelet service file from all master nodes
sudo rm -rf  /etc/systemd/system/kubelet.service.d/20-etcd-service-manager.conf
sudo systemctl daemon-reload

# create cloud-config file on all master nodes to ensure that we are able to
# use Cloud storage for PV and LoadBalancer type service
# make sure you replace "my-gcp-project-id" with your GCP project ID
cat << EOF | sudo tee /etc/kubernetes/cloud-config
[Global]
project-id = "my-gcp-project-id"
EOF

# create kubeadm config file on master-0
# note that we are configuring all components to use GCE cloud provider.
# we have also enabled signed cert for kubelet server with serverTLSBootstrap option.
# 35.185.221.115 is the external IP we reserved for our load balancer.
cat << EOF > kubeadmcfg.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: InitConfiguration
nodeRegistration:
  criSocket: "/run/containerd/containerd.sock"
  kubeletExtraArgs:
    cloud-provider: "gce"
    cloud-config: "/etc/kubernetes/cloud-config"
    cgroup-driver: "systemd"
---
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
apiServer:
  certSANs:
    - "35.185.221.115"
    - "10.240.0.10"
    - "10.240.0.11"
    - "10.240.0.12"
  extraArgs:
    cloud-provider: "gce"
    cloud-config: "/etc/kubernetes/cloud-config"
  extraVolumes:
  - name: cloud
    hostPath: "/etc/kubernetes/cloud-config"
    mountPath: "/etc/kubernetes/cloud-config"
controllerManager:
  extraArgs:
    cloud-provider: "gce"
    cloud-config: "/etc/kubernetes/cloud-config"
  extraVolumes:
  - name: cloud
    hostPath: "/etc/kubernetes/cloud-config"
    mountPath: "/etc/kubernetes/cloud-config"
networking:
  podSubnet: "192.168.0.0/16"
  serviceSubnet: "10.32.0.0/24"
controlPlaneEndpoint: "35.185.221.115:6443"
etcd:
  external:
    endpoints:
    - https://10.240.0.10:2379
    - https://10.240.0.11:2379
    - https://10.240.0.12:2379
    caFile: /etc/kubernetes/pki/etcd/ca.crt
    certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
    keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd
serverTLSBootstrap: true
EOF

Create cluster and deploy CNI plugin

# bootstrap cluster on master-0
# we are skipping some pre flight check of kubeadm since we already have etcd running on the same node
sudo kubeadm init --config kubeadmcfg.yaml \
    --upload-certs \
    --ignore-preflight-errors=DirAvailable--var-lib-etcd \
    --ignore-preflight-errors=FileAvailable--etc-kubernetes-manifests-etcd.yaml \
    --ignore-preflight-errors=Port-10250 --v=10

# configure kubectl to authenticate with API Server
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# deploy cni pluging
kubectl create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml
kubectl create -f https://docs.projectcalico.org/manifests/custom-resources.yaml

# restart coredns so that it get Pod IP using calici
kubectl rollout restart deployment coredns -n kube-system

# add master-1 and master-2 to cluster
# output of kubeadm init command should have printed join command for control plane
sudo kubeadm join 35.185.221.115:6443 --token w8wnm9.1s2phu4eac8yhqv7 \
  --discovery-token-ca-cert-hash sha256:cd8cb261e52d0b1eb449116616563467054b11f3ea9261c9645c708c3ebfa8f8 \
  --control-plane \
  --certificate-key 6444244a8bec76486f32dd1466b3a469196ba3fa98624a3fe9093f03112c45f9 \
  --ignore-preflight-errors=DirAvailable--var-lib-etcd \
  --ignore-preflight-errors=FileAvailable--etc-kubernetes-manifests-etcd.yaml \
  --ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests \
  --ignore-preflight-errors=Port-10250 --v=10

# add worker-0 to cluser
sudo kubeadm join 35.185.221.115:6443 --token w8wnm9.1s2phu4eac8yhqv7 \
  --discovery-token-ca-cert-hash sha256:cd8cb261e52d0b1eb449116616563467054b11f3ea9261c9645c708c3ebfa8f8 \
  --ignore-preflight-errors=Port-10250

# from master-0 approve the pending CSR for kubelet server cert
kubectl get csr | grep Pending | cut -d " " -f1 | xargs -I{} kubectl certificate approve {}

# add master-1 and master-2 to instance group
gcloud compute instance-groups unmanaged add-instances k8s-master \
    --zone=us-west1-c \
    --instances=master-1,master-2

Setup service account for GLBC

# create service account and give compute admin access
gcloud iam service-accounts create glbc-service-account \
  --display-name "Service Account for GLBC"

# make sure you replace my-project-name with your GCP project id
export PROJECT=my-project-name
gcloud projects add-iam-policy-binding $PROJECT \
  --member serviceAccount:glbc-service-account@${PROJECT}.iam.gserviceaccount.com \
  --role roles/compute.admin

# create key file for glbc-service-account
gcloud iam service-accounts keys create key.json --iam-account \
  glbc-service-account@${PROJECT}.iam.gserviceaccount.com

# create gce config for the ingress controller
cat << EOF > gce.conf
[global]
token-url = nil
project-id = "${PROJECT}"
network-name =  "k8s-vpc"
local-zone = "us-west1-c"
subnetwork-name = "k8s-west"
EOF

# copy the key and the config to master-0
gcloud compute scp key.json gce.conf master-0:~/

From master-0 deploy the ingress controller

# Store the key as a secret in k8s
kubectl create secret generic glbc-gcp-key --from-file=key.json -n kube-system

# create configmap to mount gce.conf on ingress controller pod
kubectl create configmap gce-config --from-file=gce.conf -n kube-system

# label the worker node with zone and region. GLBC depends on these labels
kubectl label nodes worker-0 failure-domain.beta.kubernetes.io/zone=us-west1-c
kubectl label nodes worker-0 failure-domain.beta.kubernetes.io/region=us-west-1

# deploy default backend and ingress controller
# deployment config is using outdated version and will complain about following unless replaced with apps/v1:
# no matches for kind "Deployment" in version "extensions/v1beta1" 
wget -qO- - https://raw.githubusercontent.com/kubernetes/ingress-gce/master/docs/deploy/gke/non-gcp/default-http-backend.yaml | sed 's/extensions\/v1beta1/apps\/v1/' | kubectl apply -f -
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-gce/master/docs/deploy/gke/non-gcp/rbac.yaml
wget -qO- - https://raw.githubusercontent.com/kubernetes/ingress-gce/master/docs/deploy/gke/non-gcp/glbc.yaml | sed 's/extensions\/v1beta1/apps\/v1/' | kubectl apply -f -

Create certificate for metrics server

# generate private key and use kubernetes CSR to create certificated signed by k8s CA
openssl genrsa -out server.key 2048
openssl req -new -key server.key \
  -subj "/CN=metrics-server.kube-system.svc" \
  -reqexts SAN \ 
  -config <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:metrics-server.kube-system.svc")) \
  -out server.csr
cat > metrics-server.csr.yaml <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: metrics-server.kube-system.svc
spec:
  request: $(cat server.csr | base64 | tr -d '\n')
  usages:
  - digital signature
  - key encipherment
  - server auth
EOF
kubectl apply -f metrics-server.csr.yaml
kubectl certificate approve metrics-server.kube-system.svc
kubectl -n kube-system get csr metrics-server.kube-system.svc \
  -o jsonpath='{.status.certificate}' | base64 --decode > server.crt
kubectl -n kube-system create secret tls metrics-server-cert --cert=server.crt --key=server.key

Deploy metrics-server and update configuration

# create resources from upstream metrics-server file
kubectl apply -f \
  https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# update deployment to use private key and certificate
cat > metrics-server-patch.yaml <<EOF
spec:
  template:
    spec:
      containers:
      - name: metrics-server
        args:
        - --secure-port=443
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --tls-cert-file=/etc/metrics-server/pki/tls.crt
        - --tls-private-key-file=/etc/metrics-server/pki/tls.key
        volumeMounts:
        - name: metrics-server-cert-path
          mountPath: /etc/metrics-server/pki
          readOnly: true
      volumes:
        - name: metrics-server-cert-path
          secret:
            secretName: metrics-server-cert
EOF
kubectl patch deployment metrics-server -n kube-system \
  --patch "$(cat metrics-server-patch.yaml)"

# delete the APIservice created from upstream file and create new
kubectl delete apiservice v1beta1.metrics.k8s.io
cat > metrics-apiservice.yaml <<EOF
cat > metrics-apiservice.yaml <<EOF
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.metrics.k8s.io
spec:
  service:
    name: metrics-server
    namespace: kube-system
  group: metrics.k8s.io
  version: v1beta1
  insecureSkipTLSVerify: false
  groupPriorityMinimum: 100
  versionPriority: 100
  caBundle: $(cat /etc/kubernetes/pki/ca.crt | base64 -w 0 && echo)
EOF
EOF

kubectl apply -f metrics-apiservice.yaml

Get top node

# you may seem a warning related to use-protocol-buffers flag
# it seems to be kubernetes 1.21 specific issue: https://github.com/kubernetes/kubernetes/issues/101786
kubectl top nodes
NAME       CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
master-0   249m         12%    1243Mi          32%       
master-1   182m         9%     1166Mi          30%       
master-2   167m         8%     1436Mi          37%       
worker-0   88m          4%     1027Mi          26% 

Create Ingress

cat > test-ingress.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  namespace: kube-system
spec:
  defaultBackend:
    service:
      name: default-http-backend
      port:
        number: 80
EOF

kubectl apply -f test-ingress.yaml
# wait for load balancer to be created
kubectl get ingress -n kube-system --watch

# use the load balancer IP to test
curl http://34.98.65.228/healthz
ok

Create a storage class and PVC

cat > pvc.yaml <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-demo
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 30Gi
EOF

kubectl apply -f pvc-demo.yaml
# status should be Bound
kubectl get pvc --no-headers -o custom-columns=":metadata.name,:status.phase"
pvc-demo   Bound
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment