mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-07-28 17:21:11 +00:00
892 lines
16 KiB
Markdown
892 lines
16 KiB
Markdown
# Building our own cluster (medium)
|
|
|
|
- This section assumes that you already went through
|
|
|
|
*“Building our own cluster (easy)”*
|
|
|
|
- In that section, we saw how to run each control plane component manually...
|
|
|
|
...but with an older version of Kubernetes (1.19)
|
|
|
|
- In this section, we're going to do something similar...
|
|
|
|
...but with recent versions of Kubernetes!
|
|
|
|
- Note: we won't need the lab environment of that previous section
|
|
|
|
(we're going to build a new cluster from scratch)
|
|
|
|
---
|
|
|
|
## What remains the same
|
|
|
|
- We'll use machines with Kubernetes binaries pre-downloaded
|
|
|
|
- We'll run individual components by hand
|
|
|
|
(etcd, API server, controller manager, scheduler, kubelet)
|
|
|
|
- We'll run on a single node
|
|
|
|
(but we'll be laying the groundwork to add more nodes)
|
|
|
|
- We'll get the cluster to the point where we can run and expose pods
|
|
|
|
---
|
|
|
|
## What's different
|
|
|
|
- We'll need to generate TLS keys and certificates
|
|
|
|
(because it's mandatory with recent versions of Kubernetes)
|
|
|
|
- Things will be *a little bit more* secure
|
|
|
|
(but still not 100% secure, far from it!)
|
|
|
|
- We'll use containerd instead of Docker
|
|
|
|
(you could probably try with CRI-O or another CRI engine, too)
|
|
|
|
- We'll need to set up CNI for networking
|
|
|
|
- *And we won't do everything as root this time (but we might use `sudo` a lot)*
|
|
|
|
---
|
|
|
|
## Our environment
|
|
|
|
- We will use the machine indicated as `polykube1`
|
|
|
|
- This machine:
|
|
|
|
- runs Ubuntu LTS
|
|
|
|
- has Kubernetes, etcd, and CNI binaries installed
|
|
|
|
- but nothing is running
|
|
|
|
---
|
|
|
|
## Checking our environment
|
|
|
|
- Let's make sure we have everything we need first
|
|
|
|
.lab[
|
|
|
|
- Log into the `polykube1` machine
|
|
|
|
- Check available versions:
|
|
```bash
|
|
etcd -version
|
|
kube-apiserver --version
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## The plan
|
|
|
|
We'll follow the same methodology as for the "easy" section
|
|
|
|
1. Start API server
|
|
|
|
2. Interact with it (create Deployment and Service)
|
|
|
|
3. See what's broken
|
|
|
|
4. Fix it and go back to step 2 until it works!
|
|
|
|
---
|
|
|
|
## Dealing with multiple processes
|
|
|
|
- Again, we are going to start many processes
|
|
|
|
- Depending on what you're comfortable with, you can:
|
|
|
|
- open multiple windows and multiple SSH connections
|
|
|
|
- use a terminal multiplexer like screen or tmux
|
|
|
|
- put processes in the background with `&`
|
|
<br/>(warning: log output might get confusing to read!)
|
|
|
|
---
|
|
|
|
## Starting API server
|
|
|
|
.lab[
|
|
|
|
- Try to start the API server:
|
|
```bash
|
|
kube-apiserver
|
|
# It will complain about permission to /var/run/kubernetes
|
|
|
|
sudo kube-apiserver
|
|
# Now it will complain about a bunch of missing flags, including:
|
|
# --etcd-servers
|
|
# --service-account-issuer
|
|
# --service-account-signing-key-file
|
|
```
|
|
|
|
]
|
|
|
|
Just like before, we'll need to start etcd.
|
|
|
|
But we'll also need some TLS keys!
|
|
|
|
---
|
|
|
|
## Generating TLS keys
|
|
|
|
- There are many ways to generate TLS keys (and certificates)
|
|
|
|
- A very popular and modern tool to do that is [cfssl]
|
|
|
|
- We're going to use the old-fashioned [openssl] CLI
|
|
|
|
- Feel free to use cfssl or any other tool if you prefer!
|
|
|
|
[cfssl]: https://github.com/cloudflare/cfssl#using-the-command-line-tool
|
|
[openssl]: https://www.openssl.org/docs/man3.0/man1/
|
|
|
|
---
|
|
|
|
## How many keys do we need?
|
|
|
|
At the very least, we need the following two keys:
|
|
|
|
- ServiceAccount key pair
|
|
|
|
- API client key pair, aka "CA key"
|
|
|
|
(technically, we will need a *certificate* for that key pair)
|
|
|
|
But if we wanted to tighten the cluster security, we'd need many more...
|
|
|
|
---
|
|
|
|
## The other keys
|
|
|
|
These keys are not strictly necessary at this point:
|
|
|
|
- etcd key pair
|
|
|
|
*without that key, communication with etcd will be insecure*
|
|
|
|
- API server endpoint key pair
|
|
|
|
*the API server will generate this one automatically if we don't*
|
|
|
|
- kubelet key pair (used by API server to connect to kubelets)
|
|
|
|
*without that key, commands like kubectl logs/exec will be insecure*
|
|
|
|
---
|
|
|
|
## Would you like some auth with that?
|
|
|
|
If we want to enable authentication and authorization, we also need various API client key pairs signed by the "CA key" mentioned earlier. That would include (non-exhaustive list):
|
|
|
|
- controller manager key pair
|
|
|
|
- scheduler key pair
|
|
|
|
- in most cases: kube-proxy (or equivalent) key pair
|
|
|
|
- in most cases: key pairs for the nodes joining the cluster
|
|
|
|
(these might be generated through TLS bootstrap tokens)
|
|
|
|
- key pairs for users that will interact with the clusters
|
|
|
|
(unless another authentication mechanism like OIDC is used)
|
|
|
|
---
|
|
|
|
## Generating our keys and certificates
|
|
|
|
.lab[
|
|
|
|
- Generate the ServiceAccount key pair:
|
|
```bash
|
|
openssl genrsa -out sa.key 2048
|
|
```
|
|
|
|
- Generate the CA key pair:
|
|
```bash
|
|
openssl genrsa -out ca.key 2048
|
|
```
|
|
|
|
- Generate a self-signed certificate for the CA key:
|
|
```bash
|
|
openssl x509 -new -key ca.key -out ca.cert -subj /CN=kubernetes/
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## Starting etcd
|
|
|
|
- This one is easy!
|
|
|
|
.lab[
|
|
|
|
- Start etcd:
|
|
```bash
|
|
etcd
|
|
```
|
|
|
|
]
|
|
|
|
Note: if you want a bit of extra challenge, you can try
|
|
to generate the etcd key pair and use it.
|
|
|
|
(You will need to pass it to etcd and to the API server.)
|
|
|
|
---
|
|
|
|
## Starting API server
|
|
|
|
- We need to use the keys and certificate that we just generated
|
|
|
|
.lab[
|
|
|
|
- Start the API server:
|
|
```bash
|
|
sudo kube-apiserver \
|
|
--etcd-servers=http://localhost:2379 \
|
|
--service-account-signing-key-file=sa.key \
|
|
--service-account-issuer=https://kubernetes \
|
|
--service-account-key-file=sa.key \
|
|
--client-ca-file=ca.cert
|
|
```
|
|
|
|
]
|
|
|
|
The API server should now start.
|
|
|
|
But can we really use it? 🤔
|
|
|
|
---
|
|
|
|
## Trying `kubectl`
|
|
|
|
- Let's try some simple `kubectl` command
|
|
|
|
.lab[
|
|
|
|
- Try to list Namespaces:
|
|
```bash
|
|
kubectl get namespaces
|
|
```
|
|
|
|
]
|
|
|
|
We're getting an error message like this one:
|
|
|
|
```
|
|
The connection to the server localhost:8080 was refused -
|
|
did you specify the right host or port?
|
|
```
|
|
|
|
---
|
|
|
|
## What's going on?
|
|
|
|
- Recent versions of Kubernetes don't support unauthenticated API access
|
|
|
|
- The API server doesn't support listening on plain HTTP anymore
|
|
|
|
- `kubectl` still tries to connect to `localhost:8080` by default
|
|
|
|
- But there is nothing listening there
|
|
|
|
- Our API server listens on port 6443, using TLS
|
|
|
|
---
|
|
|
|
## Trying to access the API server
|
|
|
|
- Let's use `curl` first to confirm that everything works correctly
|
|
|
|
(and then we will move to `kubectl`)
|
|
|
|
.lab[
|
|
|
|
- Try to connect with `curl`:
|
|
```bash
|
|
curl https://localhost:6443
|
|
# This will fail because the API server certificate is unknown.
|
|
```
|
|
|
|
- Try again, skipping certificate verification:
|
|
```bash
|
|
curl --insecure https://localhost:6443
|
|
```
|
|
|
|
]
|
|
|
|
We should now see an `Unauthorized` Kubernetes API error message.
|
|
</br>
|
|
We need to authenticate with our key and certificate.
|
|
|
|
---
|
|
|
|
## Authenticating with the API server
|
|
|
|
- For the time being, we can use the CA key and cert directly
|
|
|
|
- In a real world scenario, we would *never* do that!
|
|
|
|
(because we don't want the CA key to be out there in the wild)
|
|
|
|
.lab[
|
|
|
|
- Try again, skipping cert verification, and using the CA key and cert:
|
|
```bash
|
|
curl --insecure --key ca.key --cert ca.cert https://localhost:6443
|
|
```
|
|
|
|
]
|
|
|
|
We should see a list of API routes.
|
|
|
|
---
|
|
|
|
class: extra-details
|
|
|
|
## Doing it right
|
|
|
|
In the future, instead of using the CA key and certificate,
|
|
we should generate a new key, and a certificate for that key,
|
|
signed by the CA key.
|
|
|
|
Then we can use that new key and certificate to authenticate.
|
|
|
|
Example:
|
|
|
|
```
|
|
### Generate a key pair
|
|
openssl genrsa -out user.key
|
|
|
|
### Extract the public key
|
|
openssl pkey -in user.key -out user.pub -pubout
|
|
|
|
### Generate a certificate signed by the CA key
|
|
openssl x509 -new -key ca.key -force_pubkey user.pub -out user.cert \
|
|
-subj /CN=kubernetes-user/
|
|
```
|
|
|
|
---
|
|
|
|
## Writing a kubeconfig file
|
|
|
|
- We now want to use `kubectl` instead of `curl`
|
|
|
|
- We'll need to write a kubeconfig file for `kubectl`
|
|
|
|
- There are many way to do that; here, we're going to use `kubectl config`
|
|
|
|
- We'll need to:
|
|
|
|
- set the "cluster" (API server endpoint)
|
|
|
|
- set the "credentials" (the key and certficate)
|
|
|
|
- set the "context" (referencing the cluster and credentials)
|
|
|
|
- use that context (make it the default that `kubectl` will use)
|
|
|
|
---
|
|
|
|
## Set the cluster
|
|
|
|
The "cluster" section holds the API server endpoint.
|
|
|
|
.lab[
|
|
|
|
- Set the API server endpoint:
|
|
```bash
|
|
kubectl config set-cluster polykube --server=https://localhost:6443
|
|
```
|
|
|
|
- Don't verify the API server certificate:
|
|
```bash
|
|
kubectl config set-cluster polykube --insecure-skip-tls-verify
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## Set the credentials
|
|
|
|
The "credentials" section can hold a TLS key and certificate, or a token, or configuration information for a plugin (for instance, when using AWS EKS or GCP GKE, they use a plugin).
|
|
|
|
.lab[
|
|
|
|
- Set the client key and certificate:
|
|
```bash
|
|
kubectl config set-credentials polykube \
|
|
--client-key ca.key \
|
|
--client-certificate ca.cert
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## Set and use the context
|
|
|
|
The "context" section references the "cluster" and "credentials" that we defined earlier.
|
|
|
|
(It can also optionally reference a Namespace.)
|
|
|
|
.lab[
|
|
|
|
- Set the "context":
|
|
```bash
|
|
kubectl config set-context polykube --cluster polykube --user polykube
|
|
```
|
|
|
|
- Set that context to be the default context:
|
|
```bash
|
|
kubectl config use-context polykube
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## Review the kubeconfig file
|
|
|
|
The kubeconfig file should look like this:
|
|
|
|
.small[
|
|
```yaml
|
|
apiVersion: v1
|
|
clusters:
|
|
- cluster:
|
|
insecure-skip-tls-verify: true
|
|
server: https://localhost:6443
|
|
name: polykube
|
|
contexts:
|
|
- context:
|
|
cluster: polykube
|
|
user: polykube
|
|
name: polykube
|
|
current-context: polykube
|
|
kind: Config
|
|
preferences: {}
|
|
users:
|
|
- name: polykube
|
|
user:
|
|
client-certificate: /root/ca.cert
|
|
client-key: /root/ca.key
|
|
```
|
|
]
|
|
|
|
---
|
|
|
|
## Trying the kubeconfig file
|
|
|
|
- We should now be able to access our cluster's API!
|
|
|
|
.lab[
|
|
|
|
- Try to list Namespaces:
|
|
```bash
|
|
kubectl get namespaces
|
|
```
|
|
]
|
|
|
|
This should show the classic `default`, `kube-system`, etc.
|
|
|
|
---
|
|
|
|
class: extra-details
|
|
|
|
## Do we need `--client-ca-file` ?
|
|
|
|
Technically, we didn't need to specify the `--client-ca-file` flag!
|
|
|
|
But without that flag, no client can be authenticated.
|
|
|
|
Which means that we wouldn't be able to issue any API request!
|
|
|
|
---
|
|
|
|
## Running pods
|
|
|
|
- We can now try to create a Deployment
|
|
|
|
.lab[
|
|
|
|
- Create a Deployment:
|
|
```bash
|
|
kubectl create deployment blue --image=jpetazzo/color
|
|
```
|
|
|
|
- Check the results:
|
|
```bash
|
|
kubectl get deployments,replicasets,pods
|
|
```
|
|
|
|
]
|
|
|
|
Our Deployment exists, but not the Replica Set or Pod.
|
|
|
|
We need to run the controller manager.
|
|
|
|
---
|
|
|
|
## Running the controller manager
|
|
|
|
- Previously, we used the `--master` flag to pass the API server address
|
|
|
|
- Now, we need to authenticate properly
|
|
|
|
- The simplest way at this point is probably to use the same kubeconfig file!
|
|
|
|
.lab[
|
|
|
|
- Start the controller manager:
|
|
```bash
|
|
kube-controller-manager --kubeconfig .kube/config
|
|
```
|
|
|
|
- Check the results:
|
|
```bash
|
|
kubectl get deployments,replicasets,pods
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## What's next?
|
|
|
|
- Normally, the last commands showed us a Pod in `Pending` state
|
|
|
|
- We need two things to continue:
|
|
|
|
- the scheduler (to assign the Pod to a Node)
|
|
|
|
- a Node!
|
|
|
|
- We're going to run `kubelet` to register the Node with the cluster
|
|
|
|
---
|
|
|
|
## Running `kubelet`
|
|
|
|
- Let's try to run `kubelet` and see what happens!
|
|
|
|
.lab[
|
|
|
|
- Start `kubelet`:
|
|
```bash
|
|
sudo kubelet
|
|
```
|
|
|
|
]
|
|
|
|
We should see an error about connecting to `containerd.sock`.
|
|
|
|
We need to run a container engine!
|
|
|
|
(For instance, `containerd`.)
|
|
|
|
---
|
|
|
|
## Running `containerd`
|
|
|
|
- We need to install and start `containerd`
|
|
|
|
- You could try another engine if you wanted
|
|
|
|
(but there might be complications!)
|
|
|
|
.lab[
|
|
|
|
- Install `containerd`:
|
|
```bash
|
|
sudo apt-get install containerd
|
|
```
|
|
|
|
- Start `containerd`:
|
|
```bash
|
|
sudo containerd
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
class: extra-details
|
|
|
|
## Configuring `containerd`
|
|
|
|
Depending on how we install `containerd`, it might need a bit of extra configuration.
|
|
|
|
Watch for the following symptoms:
|
|
|
|
- `containerd` refuses to start
|
|
|
|
(rare, unless there is an *invalid* configuration)
|
|
|
|
- `containerd` starts but `kubelet` can't connect
|
|
|
|
(could be the case if the configuration disables the CRI socket)
|
|
|
|
- `containerd` starts and things work but Pods keep being killed
|
|
|
|
(may happen if there is a mismatch in the cgroups driver)
|
|
|
|
---
|
|
|
|
## Starting `kubelet` for good
|
|
|
|
- Now that `containerd` is running, `kubelet` should start!
|
|
|
|
.lab[
|
|
|
|
- Try to start `kubelet`:
|
|
```bash
|
|
sudo kubelet
|
|
```
|
|
|
|
- In another terminal, check if our Node is now visible:
|
|
```bash
|
|
sudo kubectl get nodes
|
|
```
|
|
|
|
]
|
|
|
|
`kubelet` should now start, but our Node doesn't show up in `kubectl get nodes`!
|
|
|
|
This is because without a kubeconfig file, `kubelet` runs in standalone mode:
|
|
<br/>
|
|
it will not connect to a Kubernetes API server, and will only start *static pods*.
|
|
|
|
---
|
|
|
|
## Passing the kubeconfig file
|
|
|
|
- Let's start `kubelet` again, with our kubeconfig file
|
|
|
|
.lab[
|
|
|
|
- Stop `kubelet` (e.g. with `Ctrl-C`)
|
|
|
|
- Restart it with the kubeconfig file:
|
|
```bash
|
|
sudo kubelet --kubeconfig .kube/config
|
|
```
|
|
|
|
- Check our list of Nodes:
|
|
```bash
|
|
kubectl get nodes
|
|
```
|
|
|
|
]
|
|
|
|
This time, our Node should show up!
|
|
|
|
---
|
|
|
|
## Node readiness
|
|
|
|
- However, our Node shows up as `NotReady`
|
|
|
|
- If we wait a few minutes, the `kubelet` logs will tell us why:
|
|
|
|
*we're missing a CNI configuration!*
|
|
|
|
- As a result, the containers can't be connected to the network
|
|
|
|
- `kubelet` detects that and doesn't become `Ready` until this is fixed
|
|
|
|
---
|
|
|
|
## CNI configuration
|
|
|
|
- We need to provide a CNI configuration
|
|
|
|
- This is a file in `/etc/cni/net.d`
|
|
|
|
(the name of the file doesn't matter; the first file in lexicographic order will be used)
|
|
|
|
- Usually, when installing a "CNI plugin¹", this file gets installed automatically
|
|
|
|
- Here, we are going to write that file manually
|
|
|
|
.footnote[¹Technically, a *pod network*; typically running as a DaemonSet, which will install the file with a `hostPath` volume.]
|
|
|
|
---
|
|
|
|
## Our CNI configuration
|
|
|
|
Create the following file in e.g. `/etc/cni/net.d/kube.conf`:
|
|
|
|
```json
|
|
{
|
|
"cniVersion": "0.3.1",
|
|
"name": "kube",
|
|
"type": "bridge",
|
|
"bridge": "cni0",
|
|
"isDefaultGateway": true,
|
|
"ipMasq": true,
|
|
"hairpinMode": true,
|
|
"ipam": {
|
|
"type": "host-local",
|
|
"subnet": "10.1.1.0/24"
|
|
}
|
|
}
|
|
```
|
|
|
|
That's all we need - `kubelet` will detect and validate the file automatically!
|
|
|
|
---
|
|
|
|
## Checking our Node again
|
|
|
|
- After a short time (typically about 10 seconds) the Node should be `Ready`
|
|
|
|
.lab[
|
|
|
|
- Wait until the Node is `Ready`:
|
|
```bash
|
|
kubectl get nodes
|
|
```
|
|
|
|
]
|
|
|
|
If the Node doesn't show up as `Ready`, check the `kubelet` logs.
|
|
|
|
---
|
|
|
|
## What's next?
|
|
|
|
- At this point, we have a `Pending` Pod and a `Ready` Node
|
|
|
|
- All we need is the scheduler to bind the former to the latter
|
|
|
|
.lab[
|
|
|
|
- Run the scheduler:
|
|
```bash
|
|
kube-scheduler --kubeconfig .kube/config
|
|
```
|
|
|
|
- Check that the Pod gets assigned to the Node and becomes `Running`:
|
|
```bash
|
|
kubectl get pods
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## Check network access
|
|
|
|
- Let's check that we can connect to our Pod, and that the Pod can connect outside
|
|
|
|
.lab[
|
|
|
|
- Get the Pod's IP address:
|
|
```bash
|
|
kubectl get pods -o wide
|
|
```
|
|
|
|
- Connect to the Pod (make sure to update the IP address):
|
|
```bash
|
|
curl `10.1.1.2`
|
|
```
|
|
|
|
- Check that the Pod has external connectivity too:
|
|
```bash
|
|
kubectl exec `blue-xxxxxxxxxx-yyyyy` -- ping -c3 1.1
|
|
```
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## Expose our Deployment
|
|
|
|
- We can now try to expose the Deployment and connect to the ClusterIP
|
|
|
|
.lab[
|
|
|
|
- Expose the Deployment:
|
|
```bash
|
|
kubectl expose deployment blue --port=80
|
|
```
|
|
|
|
- Retrieve the ClusterIP:
|
|
```bash
|
|
kubectl get services
|
|
```
|
|
|
|
- Try to connect to the ClusterIP:
|
|
```bash
|
|
curl `10.0.0.42`
|
|
```
|
|
]
|
|
|
|
At this point, it won't work - we need to run `kube-proxy`!
|
|
|
|
---
|
|
|
|
## Running `kube-proxy`
|
|
|
|
- We need to run `kube-proxy`
|
|
|
|
(also passing it our kubeconfig file)
|
|
|
|
.lab[
|
|
|
|
- Run `kube-proxy`:
|
|
```bash
|
|
sudo kube-proxy --kubeconfig .kube/config
|
|
```
|
|
|
|
- Try again to connect to the ClusterIP:
|
|
```bash
|
|
curl `10.0.0.42`
|
|
```
|
|
|
|
]
|
|
|
|
This time, it should work.
|
|
|
|
---
|
|
|
|
## What's next?
|
|
|
|
- Scale up the Deployment, and check that load balancing works properly
|
|
|
|
- Enable RBAC, and generate individual certificates for each controller
|
|
|
|
(check the [certificate paths][certpath] section in the Kubernetes documentation
|
|
for a detailed list of all the certificates and keys that are used by the
|
|
control plane, and which flags are used by which components to configure them!)
|
|
|
|
- Add more nodes to the cluster
|
|
|
|
*Feel free to try these if you want to get additional hands-on experience!*
|
|
|
|
[certpath]: https://kubernetes.io/docs/setup/best-practices/certificates/#certificate-paths
|
|
|
|
???
|
|
|
|
:EN:- Setting up control plane certificates
|
|
:EN:- Implementing a basic CNI configuration
|
|
:FR:- Mettre en place les certificats du plan de contrôle
|
|
:FR:- Réaliser un configuration CNI basique
|