Last active
February 21, 2020 15:17
-
-
Save ross-humphrey/e6a66a9b322de1e8f4954e6bfe90c9b0 to your computer and use it in GitHub Desktop.
Kubernetes The Hard Way Notes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=== Kubernetes - The Hard Way === | |
== The labs are hosted on GCP == | |
== Provisioning Compute Resource == | |
Kubernetes requires a set of machines to host the K8 control plane | |
and the worker nodes where containers are run. | |
== Networking == | |
Kubernetes assumes a flat network within its networking model in which | |
containers and nodes can communicate with each other. Where this is not | |
desired network policies can limit how groups of containers are allowed | |
to communicate with each other + external end points. | |
== Virtual Private Cloud Network == | |
Create the 'kubernetes-the-hard-way' custom VPC: (to do this the Compute Engine API must first be enabled) | |
gcloud compute networks create kubernetes-the-hard-way --subnet-mode custom | |
== Create the subnet == | |
gcloud compute networks subnets create kubernetes \ | |
--network kubernetes-the-hard-way \ | |
--range 10.240.0.0/24 | |
== Create firewall rule that allows external SSH, ICMP and HTTPS: == | |
gcloud compute firewall-rules create kubernetes-the-hard-way-allow-external \ | |
--allow tcp:22,tcp:6443,icmp \ | |
--network kubernetes-the-hard-way-162984 \ | |
--source-ranges 0.0.0.0/0 | |
gcloud compute firewall-rules create kubernetes-the-hard-way-allow-internal \ | |
--allow tcp,udp,icmp \ | |
--network kubernetes-the-hard-way-162984 \ | |
--source-ranges 10.240.0.0/24,10.200.0.0/16 | |
== List the firewall rules == | |
gcloud compute firewall-rules list --filter="network:kubernetes-the-hard-way-162984" | |
== Kubernetes Public IP (to be attached to the external load balancer) == | |
gcloud compute addresses create kubernetes-the-hard-way \ | |
--region us-west1 | |
== Verify static IP == | |
gcloud compute addresses list --filter="name=('kubernetes-the-hard-way')" | |
== Create compute instances to host Kubernetes control plane == | |
for i in 0 1 2; do | |
gcloud compute instances create controller-${i} \ | |
--async \ | |
--boot-disk-size 200GB \ | |
--can-ip-forward \ | |
--image-family ubuntu-1804-lts \ | |
--image-project ubuntu-os-cloud \ | |
--machine-type n1-standard-1 \ | |
--private-network-ip 10.240.0.1${i} \ | |
--scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \ | |
--subnet kubernetes \ | |
--tags kubernetes-the-hard-way,controller | |
done | |
== Create three compute instances to host Kubernetes worker nodes == | |
for i in 0 1 2; do | |
gcloud compute instances create worker-${i} \ | |
--async \ | |
--boot-disk-size 200GB \ | |
--can-ip-forward \ | |
--image-family ubuntu-1804-lts \ | |
--image-project ubuntu-os-cloud \ | |
--machine-type n1-standard-1 \ | |
--metadata pod-cidr=10.200.${i}.0/24 \ | |
--private-network-ip 10.240.0.2${i} \ | |
--scopes compute-rw,storage-ro,service-management,service-control,logging-write,monitoring \ | |
--subnet kubernetes \ | |
--tags kubernetes-the-hard-way,worker | |
done | |
== Test SSH Access == | |
gcloud compute ssh controller-0 | |
== Generate the Certificate Authority Configuration File, Certificate and Private Key == | |
{ | |
cat > ca-config.json <<EOF | |
{ | |
"signing": { | |
"default": { | |
"expiry": "8760h" | |
}, | |
"profiles": { | |
"kubernetes": { | |
"usages": ["signing", "key encipherment", "server auth", "client auth"], | |
"expiry": "8760h" | |
} | |
} | |
} | |
} | |
EOF | |
cat > ca-csr.json <<EOF | |
{ | |
"CN": "Kubernetes", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "Kubernetes", | |
"OU": "CA", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
cfssl gencert -initca ca-csr.json | cfssljson -bare ca | |
} | |
== Generate the Admin Client Certificate == | |
{ | |
cat > admin-csr.json <<EOF | |
{ | |
"CN": "admin", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "system:masters", | |
"OU": "Kubernetes The Hard Way", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
cfssl gencert \ | |
-ca=ca.pem \ | |
-ca-key=ca-key.pem \ | |
-config=ca-config.json \ | |
-profile=kubernetes \ | |
admin-csr.json | cfssljson -bare admin | |
} | |
== Kubelet Client Certificates == | |
Kubernetes uses a special purpose authorization node called Node Authorizer, | |
authorizes API requests made by Kubelets. | |
To be authorized by the Node authorizer, kubelets must use a credential that | |
identifies them as being in the system::nodes group, with a username of | |
system:node:<nodeName> | |
A certificate and private key for each Kubernetes worker node is generated as follows: | |
for instance in worker-0 worker-1 worker-2; do | |
cat > ${instance}-csr.json <<EOF | |
{ | |
"CN": "system:node:${instance}", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "system:nodes", | |
"OU": "Kubernetes The Hard Way", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
EXTERNAL_IP=$(gcloud compute instances describe ${instance} \ | |
--format 'value(networkInterfaces[0].accessConfigs[0].natIP)') | |
INTERNAL_IP=$(gcloud compute instances describe ${instance} \ | |
--format 'value(networkInterfaces[0].networkIP)') | |
cfssl gencert \ | |
-ca=ca.pem \ | |
-ca-key=ca-key.pem \ | |
-config=ca-config.json \ | |
-hostname=${instance},${EXTERNAL_IP},${INTERNAL_IP} \ | |
-profile=kubernetes \ | |
${instance}-csr.json | cfssljson -bare ${instance} | |
done | |
== Generate the Controller Manager Client Certificate == | |
{ | |
cat > kube-controller-manager-csr.json <<EOF | |
{ | |
"CN": "system:kube-controller-manager", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "system:kube-controller-manager", | |
"OU": "Kubernetes The Hard Way", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
cfssl gencert \ | |
-ca=ca.pem \ | |
-ca-key=ca-key.pem \ | |
-config=ca-config.json \ | |
-profile=kubernetes \ | |
kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager | |
} | |
== Generate the Kube Proxy Client Certificate == | |
{ | |
cat > kube-proxy-csr.json <<EOF | |
{ | |
"CN": "system:kube-proxy", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "system:node-proxier", | |
"OU": "Kubernetes The Hard Way", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
cfssl gencert \ | |
-ca=ca.pem \ | |
-ca-key=ca-key.pem \ | |
-config=ca-config.json \ | |
-profile=kubernetes \ | |
kube-proxy-csr.json | cfssljson -bare kube-proxy | |
} | |
== Generate the Scheduler Client Certificate == | |
{ | |
cat > kube-scheduler-csr.json <<EOF | |
{ | |
"CN": "system:kube-scheduler", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "system:kube-scheduler", | |
"OU": "Kubernetes The Hard Way", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
cfssl gencert \ | |
-ca=ca.pem \ | |
-ca-key=ca-key.pem \ | |
-config=ca-config.json \ | |
-profile=kubernetes \ | |
kube-scheduler-csr.json | cfssljson -bare kube-scheduler | |
} | |
== Generate the Kubernetes API Server Certificate == | |
{ | |
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \ | |
--region $(gcloud config get-value compute/region) \ | |
--format 'value(address)') | |
KUBERNETES_HOSTNAMES=kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.svc.cluster.local | |
cat > kubernetes-csr.json <<EOF | |
{ | |
"CN": "kubernetes", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "Kubernetes", | |
"OU": "Kubernetes The Hard Way", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
cfssl gencert \ | |
-ca=ca.pem \ | |
-ca-key=ca-key.pem \ | |
-config=ca-config.json \ | |
-hostname=10.32.0.1,10.240.0.10,10.240.0.11,10.240.0.12,${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,${KUBERNETES_HOSTNAMES} \ | |
-profile=kubernetes \ | |
kubernetes-csr.json | cfssljson -bare kubernetes | |
} | |
== Generate the Service Account Key Pair == | |
{ | |
cat > service-account-csr.json <<EOF | |
{ | |
"CN": "service-accounts", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "US", | |
"L": "Portland", | |
"O": "Kubernetes", | |
"OU": "Kubernetes The Hard Way", | |
"ST": "Oregon" | |
} | |
] | |
} | |
EOF | |
cfssl gencert \ | |
-ca=ca.pem \ | |
-ca-key=ca-key.pem \ | |
-config=ca-config.json \ | |
-profile=kubernetes \ | |
service-account-csr.json | cfssljson -bare service-account | |
} | |
== Distribute the Client And Server Certificates == | |
== Copy certificates and private keys to each worker instance == | |
for instance in worker-0 worker-1 worker-2; do | |
gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/ | |
done | |
== Copy certificates to controller instance == | |
for instance in controller-0 controller-1 controller-2; do | |
gcloud compute scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \ | |
service-account-key.pem service-account.pem ${instance}:~/ | |
done | |
== Generating Kubernetes Configuration Files for Authentication == | |
- Kubernetes configuration files (kubeconfigs) enable Kubernetes clients | |
to locate and authentiate to the Kubernetes API Servers. | |
== Client Authentication Configs == | |
- Generates kubeconfig files: | |
> controller manager | |
> kubelet | |
> kube-proxy | |
> schedule clients | |
> admin user | |
== Kubernetes Public IP Address == | |
Each kubeconfig requires a Kubernetes API server to connect to. To support | |
high availability the IP address assigned to the external load balancer | |
front the Kuberetes API Servers will be used: | |
Retrieve the IP Address: | |
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \ | |
--region us-west1 \ | |
--format 'value(address)') | |
== The kubelet Kubernetes Configuration File == | |
When generating kubeconfig files for Kubeletes the client certificate matching the | |
Kubelet's node name must be used. Ensuring Kubelets are properly authorized by the Kubernetes Node Authorizer | |
for instance in worker-0 worker-1 worker-2; do | |
kubectl config set-cluster kubernetes-the-hard-way \ | |
--certificate-authority=ca.pem \ | |
--embed-certs=true \ | |
--server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \ | |
--kubeconfig=${instance}.kubeconfig | |
kubectl config set-credentials system:node:${instance} \ | |
--client-certificate=${instance}.pem \ | |
--client-key=${instance}-key.pem \ | |
--embed-certs=true \ | |
--kubeconfig=${instance}.kubeconfig | |
kubectl config set-context default \ | |
--cluster=kubernetes-the-hard-way \ | |
--user=system:node:${instance} \ | |
--kubeconfig=${instance}.kubeconfig | |
kubectl config use-context default --kubeconfig=${instance}.kubeconfig | |
done | |
== Generate a kubeconfig file for the kube-proxy service == | |
{ | |
kubectl config set-cluster kubernetes-the-hard-way \ | |
--certificate-authority=ca.pem \ | |
--embed-certs=true \ | |
--server=https://${KUBERNETES_PUBLIC_ADDRESS}:6443 \ | |
--kubeconfig=kube-proxy.kubeconfig | |
kubectl config set-credentials system:kube-proxy \ | |
--client-certificate=kube-proxy.pem \ | |
--client-key=kube-proxy-key.pem \ | |
--embed-certs=true \ | |
--kubeconfig=kube-proxy.kubeconfig | |
kubectl config set-context default \ | |
--cluster=kubernetes-the-hard-way \ | |
--user=system:kube-proxy \ | |
--kubeconfig=kube-proxy.kubeconfig | |
kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig | |
} | |
== Generate a kubeconfig file for the kube-controller-manager service == | |
{ | |
kubectl config set-cluster kubernetes-the-hard-way \ | |
--certificate-authority=ca.pem \ | |
--embed-certs=true \ | |
--server=https://127.0.0.1:6443 \ | |
--kubeconfig=kube-controller-manager.kubeconfig | |
kubectl config set-credentials system:kube-controller-manager \ | |
--client-certificate=kube-controller-manager.pem \ | |
--client-key=kube-controller-manager-key.pem \ | |
--embed-certs=true \ | |
--kubeconfig=kube-controller-manager.kubeconfig | |
kubectl config set-context default \ | |
--cluster=kubernetes-the-hard-way \ | |
--user=system:kube-controller-manager \ | |
--kubeconfig=kube-controller-manager.kubeconfig | |
kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig | |
} | |
== Geneate a kubeconfig file for kube-scheduler service == | |
{ | |
kubectl config set-cluster kubernetes-the-hard-way \ | |
--certificate-authority=ca.pem \ | |
--embed-certs=true \ | |
--server=https://127.0.0.1:6443 \ | |
--kubeconfig=kube-scheduler.kubeconfig | |
kubectl config set-credentials system:kube-scheduler \ | |
--client-certificate=kube-scheduler.pem \ | |
--client-key=kube-scheduler-key.pem \ | |
--embed-certs=true \ | |
--kubeconfig=kube-scheduler.kubeconfig | |
kubectl config set-context default \ | |
--cluster=kubernetes-the-hard-way \ | |
--user=system:kube-scheduler \ | |
--kubeconfig=kube-scheduler.kubeconfig | |
kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig | |
} | |
== Generate a kubeconfig file for the admin user == | |
{ | |
kubectl config set-cluster kubernetes-the-hard-way \ | |
--certificate-authority=ca.pem \ | |
--embed-certs=true \ | |
--server=https://127.0.0.1:6443 \ | |
--kubeconfig=admin.kubeconfig | |
kubectl config set-credentials admin \ | |
--client-certificate=admin.pem \ | |
--client-key=admin-key.pem \ | |
--embed-certs=true \ | |
--kubeconfig=admin.kubeconfig | |
kubectl config set-context default \ | |
--cluster=kubernetes-the-hard-way \ | |
--user=admin \ | |
--kubeconfig=admin.kubeconfig | |
kubectl config use-context default --kubeconfig=admin.kubeconfig | |
} | |
== Distribute the kubernetes configuration files == | |
== Copy the kubelet and kube-proxy kubeconfig files to each worker instance | |
for instance in worker-0 worker-1 worker-2; do | |
gcloud compute scp ${instance}.kubeconfig kube-proxy.kubeconfig ${instance}:~/ | |
done | |
== Copy the appropiate kube-controller-manager and kube-scheduler kubeconfig files to each controller instance | |
for instance in controller-0 controller-1 controller-2; do | |
gcloud compute scp admin.kubeconfig kube-controller-manager.kubeconfig kube-scheduler.kubeconfig ${instance}:~/ | |
done | |
== Generating the Data Encrpytion Config and Key == | |
Kubernetes stores a variety of data including cluster state, application configurations and secrets. | |
Kubernetes supports the ability to encrpyt at rest | |
Generate an encryption key: | |
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64) | |
Create the encrpytion-config.yaml | |
cat > encryption-config.yaml <<EOF | |
kind: EncryptionConfig | |
apiVersion: v1 | |
resources: | |
- resources: | |
- secrets | |
providers: | |
- aescbc: | |
keys: | |
- name: key1 | |
secret: ${ENCRYPTION_KEY} | |
- identity: {} | |
EOF | |
== Copy the encrpytion-config.yaml encryption config file to each controller instance: | |
for instance in controller-0 controller-1 controller-2; do | |
gcloud compute scp encryption-config.yaml ${instance}:~/ | |
done | |
== Bootstrapping the etcd Cluster = | |
== Login to each controller instance == | |
gcloud compute ssh controller-XXX (where XXX is 0-2) | |
== Bootstrapping an etcd Cluster Member == | |
== Download and Install the etcd Binaries | |
wget -q --show-progress --https-only --timestamping \ | |
"https://github.com/etcd-io/etcd/releases/download/v3.4.0/etcd-v3.4.0-linux-amd64.tar.gz" | |
{ | |
tar -xvf etcd-v3.4.0-linux-amd64.tar.gz | |
sudo mv etcd-v3.4.0-linux-amd64/etcd* /usr/local/bin/ | |
} | |
== Configure the etcd Server == | |
{ | |
sudo mkdir -p /etc/etcd /var/lib/etcd | |
sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/ | |
} | |
Instance internal IP will be used to serve client requests and communicate | |
with etcd cluster peers. Retrieve the internal IP address for the current compute | |
instance. | |
INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \ | |
http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip) | |
Each etcd member must have a unique name within an etcd cluster. Set the etcd | |
name to match the hostname of the current compute instance | |
ETCD_NAME=$(hostname -s) | |
== Create the etcd.service systemd unit file == | |
cat <<EOF | sudo tee /etc/systemd/system/etcd.service | |
[Unit] | |
Description=etcd | |
Documentation=https://github.com/coreos | |
[Service] | |
Type=notify | |
ExecStart=/usr/local/bin/etcd \\ | |
--name ${ETCD_NAME} \\ | |
--cert-file=/etc/etcd/kubernetes.pem \\ | |
--key-file=/etc/etcd/kubernetes-key.pem \\ | |
--peer-cert-file=/etc/etcd/kubernetes.pem \\ | |
--peer-key-file=/etc/etcd/kubernetes-key.pem \\ | |
--trusted-ca-file=/etc/etcd/ca.pem \\ | |
--peer-trusted-ca-file=/etc/etcd/ca.pem \\ | |
--peer-client-cert-auth \\ | |
--client-cert-auth \\ | |
--initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\ | |
--listen-peer-urls https://${INTERNAL_IP}:2380 \\ | |
--listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \\ | |
--advertise-client-urls https://${INTERNAL_IP}:2379 \\ | |
--initial-cluster-token etcd-cluster-0 \\ | |
--initial-cluster controller-0=https://10.240.0.10:2380,controller-1=https://10.240.0.11:2380,controller-2=https://10.240.0.12:2380 \\ | |
--initial-cluster-state new \\ | |
--data-dir=/var/lib/etcd | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Start the etcd Server == | |
{ | |
sudo systemctl daemon-reload | |
sudo systemctl enable etcd | |
sudo systemctl start etcd | |
} | |
== Verification == | |
== List the etcd cluster members == | |
sudo ETCDCTL_API=3 etcdctl member list \ | |
--endpoints=https://127.0.0.1:2379 \ | |
--cacert=/etc/etcd/ca.pem \ | |
--cert=/etc/etcd/kubernetes.pem \ | |
--key=/etc/etcd/kubernetes-key.pem | |
== Bootstrapping the Kubernetes Control Plane == | |
- Bootsrap K9 control plane across three compute instances and configure for high availability | |
- Create an external load balancer that exposes K8 API Server to remove clients. | |
- API Server, Scheduler and Controller Manager will be installed on each server. | |
Log on to each box | |
Create Kubernetes configuration directory: | |
sudo mkdir -p /etc/kubernetes/config | |
== Download and Install the Kubernetes Controller Binaries == | |
wget -q --show-progress --https-only --timestamping \ | |
"https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-apiserver" \ | |
"https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-controller-manager" \ | |
"https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-scheduler" \ | |
"https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl" | |
== Install the Kubernetes Binaries == | |
{ | |
chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl | |
sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/ | |
} | |
== Configure the Kubernetes API Server == | |
{ | |
sudo mkdir -p /var/lib/kubernetes/ | |
sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \ | |
service-account-key.pem service-account.pem \ | |
encryption-config.yaml /var/lib/kubernetes/ | |
} | |
== Instance internal IP used to advertise the API Server to members of the cluster == | |
INTERNAL_IP=$(curl -s -H "Metadata-Flavor: Google" \ | |
http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip) | |
== Create the kube-apiserver.service systemd unit file== | |
cat <<EOF | sudo tee /etc/systemd/system/kube-apiserver.service | |
[Unit] | |
Description=Kubernetes API Server | |
Documentation=https://github.com/kubernetes/kubernetes | |
[Service] | |
ExecStart=/usr/local/bin/kube-apiserver \\ | |
--advertise-address=${INTERNAL_IP} \\ | |
--allow-privileged=true \\ | |
--apiserver-count=3 \\ | |
--audit-log-maxage=30 \\ | |
--audit-log-maxbackup=3 \\ | |
--audit-log-maxsize=100 \\ | |
--audit-log-path=/var/log/audit.log \\ | |
--authorization-mode=Node,RBAC \\ | |
--bind-address=0.0.0.0 \\ | |
--client-ca-file=/var/lib/kubernetes/ca.pem \\ | |
--enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\ | |
--etcd-cafile=/var/lib/kubernetes/ca.pem \\ | |
--etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\ | |
--etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\ | |
--etcd-servers=https://10.240.0.10:2379,https://10.240.0.11:2379,https://10.240.0.12:2379 \\ | |
--event-ttl=1h \\ | |
--encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\ | |
--kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\ | |
--kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\ | |
--kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\ | |
--kubelet-https=true \\ | |
--runtime-config=api/all \\ | |
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\ | |
--service-cluster-ip-range=10.32.0.0/24 \\ | |
--service-node-port-range=30000-32767 \\ | |
--tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\ | |
--tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\ | |
--v=2 | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Configure the Kubernetes Controller Manager == | |
Move the kube-controller-manager kubeconfig into place: | |
sudo mv kube-controller-manager.kubeconfig /var/lib/kubernetes/ | |
== Create the kube-controller-manager.service systemd unit file == | |
cat <<EOF | sudo tee /etc/systemd/system/kube-controller-manager.service | |
[Unit] | |
Description=Kubernetes Controller Manager | |
Documentation=https://github.com/kubernetes/kubernetes | |
[Service] | |
ExecStart=/usr/local/bin/kube-controller-manager \\ | |
--address=0.0.0.0 \\ | |
--cluster-cidr=10.200.0.0/16 \\ | |
--cluster-name=kubernetes \\ | |
--cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\ | |
--cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\ | |
--kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\ | |
--leader-elect=true \\ | |
--root-ca-file=/var/lib/kubernetes/ca.pem \\ | |
--service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\ | |
--service-cluster-ip-range=10.32.0.0/24 \\ | |
--use-service-account-credentials=true \\ | |
--v=2 | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Configure the Kubernetes Scheduler == | |
== Move the kube-scheduler kubeconfig into place == | |
sudo mv kube-scheduler.kubeconfig /var/lib/kubernetes/ | |
== Create the kube-scheduler.yaml configuration file == | |
cat <<EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yaml | |
apiVersion: kubescheduler.config.k8s.io/v1alpha1 | |
kind: KubeSchedulerConfiguration | |
clientConnection: | |
kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig" | |
leaderElection: | |
leaderElect: true | |
EOF | |
== Create the kube-scheduler.service systemd unit file == | |
cat <<EOF | sudo tee /etc/systemd/system/kube-scheduler.service | |
[Unit] | |
Description=Kubernetes Scheduler | |
Documentation=https://github.com/kubernetes/kubernetes | |
[Service] | |
ExecStart=/usr/local/bin/kube-scheduler \\ | |
--config=/etc/kubernetes/config/kube-scheduler.yaml \\ | |
--v=2 | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Start the controller services == | |
{ | |
sudo systemctl daemon-reload | |
sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler | |
sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler | |
} | |
== Enable HTTP Health Checks == | |
- Google Network Load Balancer will be used to distribute traffic across the three API servers and allow | |
each API server to terminate TLS connections and validate client ceritificates. The load balancer | |
only supports HTTP health checks which means the HTTPS endpoint exposed by the API server cant be used. | |
As a workaround the nginx webserver can be used to proxy HTTP health checks. | |
> nginx installed and configured to accetp HTTP health checks on port 80 and proxy to 6443/healthz | |
== Install basic web server to handle HTTP health checks = | |
sudo apt-get update | |
sudo apt-get install -y nginx | |
cat > kubernetes.default.svc.cluster.local <<EOF | |
server { | |
listen 80; | |
server_name kubernetes.default.svc.cluster.local; | |
location /healthz { | |
proxy_pass https://127.0.0.1:6443/healthz; | |
proxy_ssl_trusted_certificate /var/lib/kubernetes/ca.pem; | |
} | |
} | |
EOF | |
== | |
{ | |
sudo mv kubernetes.default.svc.cluster.local \ | |
/etc/nginx/sites-available/kubernetes.default.svc.cluster.local | |
sudo ln -s /etc/nginx/sites-available/kubernetes.default.svc.cluster.local /etc/nginx/sites-enabled/ | |
} | |
== | |
sudo systemctl restart nginx | |
sudo systemctl enable nginx | |
== VERIFICATION == | |
kubectl get componentstatuses --kubeconfig admin.kubeconfig | |
== Test the nginx HTTP health check proxy == | |
curl -H "Host: kubernetes.default.svc.cluster.local" -i http://127.0.0.1/healthz | |
== RBAC for Kubelet Authorization == | |
Configures RBAC permissions to allow Kubernetes API server to access the Kubelet API on | |
each worker node. Access to the Kubelet API is required for retrieving metrics, logs | |
and executing commands in pods | |
== Create the system:kube-apiserver-to-kubelet== | |
Create the above permissions to access the Kubelet API and perform most common tasks | |
associated with managing pods | |
cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f - | |
apiVersion: rbac.authorization.k8s.io/v1beta1 | |
kind: ClusterRole | |
metadata: | |
annotations: | |
rbac.authorization.kubernetes.io/autoupdate: "true" | |
labels: | |
kubernetes.io/bootstrapping: rbac-defaults | |
name: system:kube-apiserver-to-kubelet | |
rules: | |
- apiGroups: | |
- "" | |
resources: | |
- nodes/proxy | |
- nodes/stats | |
- nodes/log | |
- nodes/spec | |
- nodes/metrics | |
verbs: | |
- "*" | |
EOF | |
The kubernetes API server authenticates to the Kubelet as the kuberentes user using | |
the client certificate as defined by the --kubelet-client-certificate flag. | |
Bind the system:kube-apiserver-to-kubelet ClusterRole to the kubernetes user: | |
cat <<EOF | kubectl apply --kubeconfig admin.kubeconfig -f - | |
apiVersion: rbac.authorization.k8s.io/v1beta1 | |
kind: ClusterRoleBinding | |
metadata: | |
name: system:kube-apiserver | |
namespace: "" | |
roleRef: | |
apiGroup: rbac.authorization.k8s.io | |
kind: ClusterRole | |
name: system:kube-apiserver-to-kubelet | |
subjects: | |
- apiGroup: rbac.authorization.k8s.io | |
kind: User | |
name: kubernetes | |
EOF | |
== The Kubernetes Frontend Load Balancer == | |
Provision the external load balancer to front the K8 API Servers. | |
The kubernetes-the-hard-way static IP will be attached to the load balancer | |
Run this on the machine used to create the compute resources | |
{ | |
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \ | |
--region us-west1 \ | |
--format 'value(address)') | |
gcloud compute http-health-checks create kubernetes \ | |
--description "Kubernetes Health Check" \ | |
--host "kubernetes.default.svc.cluster.local" \ | |
--request-path "/healthz" | |
gcloud compute firewall-rules create kubernetes-the-hard-way-allow-health-check \ | |
--network kubernetes-the-hard-way \ | |
--source-ranges 209.85.152.0/22,209.85.204.0/22,35.191.0.0/16 \ | |
--allow tcp | |
gcloud compute target-pools create kubernetes-target-pool \ | |
--http-health-check kubernetes | |
gcloud compute target-pools add-instances kubernetes-target-pool \ | |
--instances controller-0,controller-1,controller-2 | |
gcloud compute forwarding-rules create kubernetes-forwarding-rule \ | |
--address ${KUBERNETES_PUBLIC_ADDRESS} \ | |
--ports 6443 \ | |
--region us-west1 \ | |
--target-pool kubernetes-target-pool | |
} | |
== VERIFICATION == | |
Retrive the static IP: | |
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \ | |
--region us-west1 \ | |
--format 'value(address)') | |
Make a HTTP request for the kubernetes version info (uses -k so less secure as cant verify legitimacy of the server) | |
url --cacert ca.pem -k https://${KUBERNETES_PUBLIC_ADDRESS}/version | |
== Bootstrapping the Kubernetes Worker Nodes == | |
== The following is installed on each node, runc, container networking plugins, containerd, kubelet, kube-proxy == | |
== Log in to the machine == | |
gcloud compute ssh worker-0 | |
== Provision a Kubernetes Worker Node == | |
== RUN THIS == | |
KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way --region us-west1) | |
== Install dependencies == | |
{ | |
sudo apt-get update | |
sudo apt-get -y install socat conntrack ipset | |
} | |
== Disable Swap == | |
By default the kubelet will fail to start if swap is enabled. It is recommended | |
therefore that it is disabled to ensure Kubernetes can provide proper resource allocation | |
and QOS | |
== Check if Swap is On == | |
sudo swapon --show | |
==If empty then fine - if enabled then run == | |
sudo swapoff -a | |
== Download and Install Worker Binaries == | |
wget -q --show-progress --https-only --timestamping \ | |
https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.15.0/crictl-v1.15.0-linux-amd64.tar.gz \ | |
https://github.com/opencontainers/runc/releases/download/v1.0.0-rc8/runc.amd64 \ | |
https://github.com/containernetworking/plugins/releases/download/v0.8.2/cni-plugins-linux-amd64-v0.8.2.tgz \ | |
https://github.com/containerd/containerd/releases/download/v1.2.9/containerd-1.2.9.linux-amd64.tar.gz \ | |
https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl \ | |
https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kube-proxy \ | |
https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubelet | |
== Create Installation Directories == | |
sudo mkdir -p \ | |
/etc/cni/net.d \ | |
/opt/cni/bin \ | |
/var/lib/kubelet \ | |
/var/lib/kube-proxy \ | |
/var/lib/kubernetes \ | |
/var/run/kubernetes | |
== Install the worker binaries == | |
{ | |
mkdir containerd | |
tar -xvf crictl-v1.15.0-linux-amd64.tar.gz | |
tar -xvf containerd-1.2.9.linux-amd64.tar.gz -C containerd | |
sudo tar -xvf cni-plugins-linux-amd64-v0.8.2.tgz -C /opt/cni/bin/ | |
sudo mv runc.amd64 runc | |
chmod +x crictl kubectl kube-proxy kubelet runc | |
sudo mv crictl kubectl kube-proxy kubelet runc /usr/local/bin/ | |
sudo mv containerd/bin/* /bin/ | |
} | |
== Configure CNI Networking == | |
== Retrieve the Pod CIDR range for the current compute instance == | |
POD_CIDR=$(curl -s -H "Metadata-Flavor: Google" \ | |
http://metadata.google.internal/computeMetadata/v1/instance/attributes/pod-cidr) | |
== Create the birdge network configuration file == | |
cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf | |
{ | |
"cniVersion": "0.3.1", | |
"name": "bridge", | |
"type": "bridge", | |
"bridge": "cnio0", | |
"isGateway": true, | |
"ipMasq": true, | |
"ipam": { | |
"type": "host-local", | |
"ranges": [ | |
[{"subnet": "${POD_CIDR}"}] | |
], | |
"routes": [{"dst": "0.0.0.0/0"}] | |
} | |
} | |
EOF | |
== Create the loopback network configuration file == | |
cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf | |
{ | |
"cniVersion": "0.3.1", | |
"name": "lo", | |
"type": "loopback" | |
} | |
EOF | |
== Configure containerd == | |
== Create the containerd configuration file == | |
sudo mkdir -p /etc/containerd/ | |
cat << EOF | sudo tee /etc/containerd/config.toml | |
[plugins] | |
[plugins.cri.containerd] | |
snapshotter = "overlayfs" | |
[plugins.cri.containerd.default_runtime] | |
runtime_type = "io.containerd.runtime.v1.linux" | |
runtime_engine = "/usr/local/bin/runc" | |
runtime_root = "" | |
EOF | |
== Create the containerd service systemd unit file == | |
cat <<EOF | sudo tee /etc/systemd/system/containerd.service | |
[Unit] | |
Description=containerd container runtime | |
Documentation=https://containerd.io | |
After=network.target | |
[Service] | |
ExecStartPre=/sbin/modprobe overlay | |
ExecStart=/bin/containerd | |
Restart=always | |
RestartSec=5 | |
Delegate=yes | |
KillMode=process | |
OOMScoreAdjust=-999 | |
LimitNOFILE=1048576 | |
LimitNPROC=infinity | |
LimitCORE=infinity | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Configure the Kubelet == | |
{ | |
sudo mv ${HOSTNAME}-key.pem ${HOSTNAME}.pem /var/lib/kubelet/ | |
sudo mv ${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig | |
sudo mv ca.pem /var/lib/kubernetes/ | |
} | |
== Create the kubelet-config.yaml configuration file == | |
cat <<EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml | |
kind: KubeletConfiguration | |
apiVersion: kubelet.config.k8s.io/v1beta1 | |
authentication: | |
anonymous: | |
enabled: false | |
webhook: | |
enabled: true | |
x509: | |
clientCAFile: "/var/lib/kubernetes/ca.pem" | |
authorization: | |
mode: Webhook | |
clusterDomain: "cluster.local" | |
clusterDNS: | |
- "10.32.0.10" | |
podCIDR: "${POD_CIDR}" | |
resolvConf: "/run/systemd/resolve/resolv.conf" | |
runtimeRequestTimeout: "15m" | |
tlsCertFile: "/var/lib/kubelet/${HOSTNAME}.pem" | |
tlsPrivateKeyFile: "/var/lib/kubelet/${HOSTNAME}-key.pem" | |
EOF | |
== The resolvConf configuration is used to avoid loops when using CoreDNS for service discovery on systems running systemd-resolved. == | |
== Create the kubelet.service systemd unit file == | |
cat <<EOF | sudo tee /etc/systemd/system/kubelet.service | |
[Unit] | |
Description=Kubernetes Kubelet | |
Documentation=https://github.com/kubernetes/kubernetes | |
After=containerd.service | |
Requires=containerd.service | |
[Service] | |
ExecStart=/usr/local/bin/kubelet \\ | |
--config=/var/lib/kubelet/kubelet-config.yaml \\ | |
--container-runtime=remote \\ | |
--container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\ | |
--image-pull-progress-deadline=2m \\ | |
--kubeconfig=/var/lib/kubelet/kubeconfig \\ | |
--network-plugin=cni \\ | |
--register-node=true \\ | |
--v=2 | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Configure the Kubernetes Proxy == | |
sudo mv kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig | |
== Create the kube-proxy-config.yaml configuration file == | |
cat <<EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml | |
kind: KubeProxyConfiguration | |
apiVersion: kubeproxy.config.k8s.io/v1alpha1 | |
clientConnection: | |
kubeconfig: "/var/lib/kube-proxy/kubeconfig" | |
mode: "iptables" | |
clusterCIDR: "10.200.0.0/16" | |
EOF | |
== kube-proxy.service systemd unit file == | |
cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service | |
[Unit] | |
Description=Kubernetes Kube Proxy | |
Documentation=https://github.com/kubernetes/kubernetes | |
[Service] | |
ExecStart=/usr/local/bin/kube-proxy \\ | |
--config=/var/lib/kube-proxy/kube-proxy-config.yaml | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Create the kube-proxy.service systemd unit file == | |
cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service | |
[Unit] | |
Description=Kubernetes Kube Proxy | |
Documentation=https://github.com/kubernetes/kubernetes | |
[Service] | |
ExecStart=/usr/local/bin/kube-proxy \\ | |
--config=/var/lib/kube-proxy/kube-proxy-config.yaml | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=multi-user.target | |
EOF | |
== Start the Worker Services == | |
{ | |
sudo systemctl daemon-reload | |
sudo systemctl enable containerd kubelet kube-proxy | |
sudo systemctl start containerd kubelet kube-proxy | |
} | |
== Verification == | |
gcloud compute ssh controller-0 \ | |
--command "kubectl get nodes --kubeconfig admin.kubeconfig" | |
--- AT THIS POINT ALL THE COMPUTE HAS GONE SQUARE - LOOKS LIKE THE CERTS ARE SOMEHOW SCREWED -- | |
Controller-0 has completely the wrong set of certifiates - and trying to send them across is not | |
working (not appearing in the directory??) Likely need to blow away the project and start again :( | |
This time round I will learn how to use TMUX properly - it should make catching up a lot quicker! | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment