studying of docker, containerd, kubernetes, k3s, k8s, ELK stack etc
-
-
- A dockerfile is a set of commands to configure and create a image.
It is possible to upload and read images only by the docker hub and automate prerequisites by shell, but it is ideal and faster to create and load images with your servers specific needs. -
This command will create a image called
my-image-name
reading aDockerfile
in the path.
.
- A dockerfile is a set of commands to configure and create a image.
-
-
This keyword is used to import the base image of the dockerfile.
The most common case of
FROM python:3.10.13-alpine3.18
FROM
is by the hubs repository:tagName.
Tag names are created by the providers of the image and mostly contains of OS information and versions.
ex : python3.10 installed in alpine3.18 OS. -
This keyword specifies starting directory within the image.
If the directory does not exists, it will create the specified directory.
You can always change the directory multiple times in order to specify different locations for executions likeCOPY
orRUN
. -
Copies the file in the scope of the dockerfile project to the images location.
COPY /location-to-copy /in-image-location
Creates a environment variable in the image.ENV LOG_PATH=/etc/project/log
-
Both
FROM python:3.10.13-alpine3.18 WORKDIR /etc/project COPY main.py RUN mkdir external-files \ && apk --no-cache update RUN apk --no-cache add curl CMD curl www.google.com \ python main.py
RUN
andCMD
receives shell commands but has a fundamental difference.
TheRUN
command is for creating the dockerfile.
And theCMD
command is for commands to run at execution.
If you rundocker build .
with the dockerfile above, you will get this.
Note that each separation ofRUN
is separated as image layers.
You should divide them to reasonable layers in order to debug efficiently.
If you rundocker run
to the created image, you will get a http response of google and run your python script. -
This dockerfile creates a tomcat container and opens the default port used by tomcat, 8080.
FROM tomcat:10.1.17-jdk21-temurin-jammy EXPOSE 8080/tcp
If you run thedocker build -t test-container . docker run test-container
docker ps
command, you can see8080/tcp
in the PORTS section.
But if you create a dockerfile withoutEXPOSE
and run docker with using theFROM tomcat:10.1.17-jdk21-temurin-jammy
-p
commanddocker build -t test-container . docker run -p 8888:8080/tcp test-container
docker ps
command, returns0.0.0.0:8888->8080/tcp
. When a docker port needs to be accessed with other docker containers,EXPOSE
is sufficient.
EXPOSE
does not map the opened port with the hosts port.
You should use it if the application only needs to be accessed by other containers within the scope.
In the other handdocker run -p <hostPort>:<containerPort>/tcp
opens and maps the port to be accessed by the host.
Therefor thedocker ps
shows0.0.0.0:8888 (the hosts port)->8080/tcp (mapped to the containers port)
.
It would be best if we use this accordingly to our applications use case.
-
This keyword is used to import the base image of the dockerfile.
-
-
-
Lets say that you want to create a service.
You would want to create a database, a backend server, and a frontend server.
Well, docker compose got you covered, you can deploy all of these servers with a single command,docker compose up
.
It is just like a composer in a orchestra, handling multiple pods just in one go.
-
-
This
version: "3" services: frontend: build: frontend_file/. ports: - "8081:8080" backend: build: backend_file/. ports: - "8082:3000" database: image: "mysql:8.2.0"
docker-compose.yaml
means,- Create 3 services named
frontend
,backend
,database
. - The
Dockerfile
located for each service is infrontend_file/.
,backend_file/.
. - For the database, use the image
mysql:8.2.0
from docker hub. - Connect the
frontend
pods port 8080 to 8081 of the host. - Connect the
backend
pods port 3000 to 8082 of the host.
- Create 3 services named
-
In docker, you can run
docker run -v <host-path>:<container-path> <image-name>
command in order use a path of the host as volumes to mount on your image.
This is handy when you need to create multiple pods and shared volumes between services.The volumes is an array ofversion: "3" services: database: image: mysql:8.2.0 volumes: - ./host-file-path:/var/lib/mysql
<host_path>:<container_path>
that you want to mount.
After you mount a volume, changes either in the host or the container will affect each other as it is a "mounted" volume.
try thedocker exec -it <mycontainer> bash
command, to double check.
Some IDEs like jetbrains allow you to connect by UI.
-
if you have ran theimage: mysql:8.2.0
image above, you will see this error. This is because the environment variable for the mysql connection is not set.You can also set environment variables in docker-compose.version: "3" services: my-service-name: image: my-image-name environment: - MYSQL_ROOT_PASSWORD=value1 - MYSQL_ALLOW_EMPTY_PASSWORD=value2 - MYSQL_RANDOM_ROOT_PASSWORD=value3
We did learn thatENV
in Dockerfile also changes the environment variable.
It would be a matter of taste where to put it, so just pick one, and do not cross use it. -
This keyword is for a service to start only after the service that it depends on.
The frontend should never start before the backend.version: '3' services: backend: image: backend-image-location frontend: image: frontend-image-location depends_on: - backend
-
Just note that this feature exists, but do not use this unless you are using docker swarm.
Most companies do not use swarm over k8s.
Also, you cannot bind 3 replicas ports with localhost port of 8080.
version: "3" services: database: image: "mysql:8.2.0" ports: - "8080:3000" deploy: replicas: 3
So you should create 3 services separately or remove the host port.
If you are thinking of creating a load balancer withindocker-compose
and redistribute traffic, I strongly advise you to just use kubernetes service and deployments.
It is possible with nginx, but not recommended. kubernetes should handle replicas and traffic, not docker.
-
-
-
Why do we need microservices? Mastering chaos - Netflix
From the year 2000, Netflix had a monolith architecture that scales horizontally.
They used two databases "STORE" and "BILLING" which had no replicas whatsoever.
As time goes on, their main server has become this massive chunk of code which was over their heads, dies constantly, takes all day to boot and debug.That is where the microservice architecture comes in.
This architecture is where a service is divided into multiple parts.
Netflix- user-api
- product-api
- platform-api
- Persistence (Databases)
- ...
This did solve the main problems of a monolith architecture, but it has its downfalls.
-
When a feature accesses multiple api, a single point that fails will result in another point to fail and continues until it kills the whole system.
In Netflix, they have solved it by a static response to be returned in case of a failure.
This blocks and isolates the failed endpoint. and continues the service with failure in mind. -
If a service wants to change data to 3 different regions of databases, but cannot access to some of them, should the process fail?
Should the process write to one, and apply the changes later?
Now netflix used a nosql database called "Cassandra" which manages multiple database nodes and keeps persistence with each other nodes.
This might be a overkill for most companies, but it's good to know a service that handles databases the "microservice" way. -
If a node keeps a "state", it means that it stores data within that node that is relevant to the service.
The "user" must keep contacting this "service node" because it keeps it's "state" within.
If a service is "stateful" the loss is a "cost" that results in a failure of a service.
If a service is "stateless" the loss of a node has no impact on the service as the user can contact another node.
-
Kubernetes is a microservice management tool constructed with multiple components.
- Master nodes
- API Server
- Kubernetes dashboard
- API
- Kubectl
- Control Manager
- Scheduler
- etcd
- Deployment
- Service
- API Server
- Worker nodes
- Pods
- Containers
- Pods
I will be giving examples on how to set up a kubernetes server without a cloud provider or tools.
If you want to manually configure kubernetes manually, you can use kubeadm, kpops or kubespray.
If you want to configure kubernetes in AWS, you can use EKS or EKSCTL.
In this guide, I will use kubeadm studied from the official documentation.-
-
In order for the Master node and worker nodes to function, you need these specific network rules.
Now if you are using AWS, you can configure security groups for inbound and outbound rules.
You can use netcat (nc) in order to check if the ports are open and connectable between nodes.
For example, if you want to check if the port10250
is open in your worker node,
runnc -l 10250
in your worker,
runnc -zv <worker-node-ec2-ip> 10250
in your master.
your master netcat should return something like this.
-
Now the kubernetes documents notes that you can run with a multiple of options like containerd, docker engine, CRI-O. I will just install docker with the simple
sudo apt install
command.
Kubernetes did drop docker support for dockershim from its project and uses containerd, but docker uses containerd under the hood, so we shouldn't be worried about it.
Install docker on ubuntu# Add Docker's official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
-
This installation is using the
apt
package install method, so if you are using redhat, or any other distributions, check instructions.sudo apt-get update # apt-transport-https may be a dummy package; if so, you can skip that package sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl
-
sudo kubeadm init --pod-network-cidr={your cidr ip}/{your cidr mask}
--pod-network-cidr=172.31.0.0/16
this command is for configuring the cidr block for pods.
--control-plane-endpoint
specifies the endpoint (load balancer or IP) for the control plane. This is used in HA setups where the control plane is distributed across multiple nodes.
--upload-certs
is used to upload certificates to the Kubernetes configuration directory. This is essential for joining additional control plane nodes to the cluster.
If have successfully installed the master node, this should pop up.
You must configure the./kube/config
file afterward.mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/kubelet.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # restart kubelet in order to let the configuration apply sudo systemctl restart kubelet.service
-
If you have successfully installed the master node, you will get a response like this.
kubeadm join 172.31.14.79:6443 --token 8xwmlv.gs5787m8y16ty0zx \ --discovery-token-ca-cert-hash sha256:0ddf616b72ad2065e6312b17f03c0a77e090547a5e8190296141a5833a5d6a6c
Run the command on the worker node. and you should get a response like this.
If you return to your master node, and checkkubectl get nodes
,
Congrats! -
If you have any errors, run the
kubeadm reset
to reset changes made by kubeadm.
Errors I have encountered
kubernetes-sigs/cri-tools#1089validate service connection: validate CRI v1 runtime API for endpoint "unix:///run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService
This error happens because kubeadm cannot communicate with containerd via a sock communication tool
crictl
.sudo crictl -r unix:///run/containerd/containerd.sock ps # expected result CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
This command will change the
/etc/containerd/config.toml
so that it uses the default configuration.
Change thedisabled_plugins = ["ctl"]
intodisabled_plugins = []
sudo su containerd config default | tee /etc/containerd/config.toml sudo vim /etc/containerd/config.toml systemctl restart containerd
-
Get "https://{server_ip_address}:6443/api?timeout=32s": dial tcp {server_ip_address}:6443: connect: connection refused
If the {server_ip_address} is localhost, check the command
kubectl config view
command.
This command would show that~/.kube/config
is not set.
https://stackoverflow.com/questions/76841889/kubectl-error-memcache-go265-couldn-t-get-current-server-api-group-list-getmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/kubelet.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
If your configurations are set, it means that you did not reboot kubernetes to set changed configurations.
# restart kubelet in order to let the configuration apply sudo systemctl restart kubelet.service
-
Unable to connect to the server: tls: failed to verify certificate: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")
This error happens because the configurations that is set on
$HOME/.kube/config
is having problems with certification.user: client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
The .pem files might not exist, if this happens on the control plane you can copy the admin.conf as the configurations.
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
-
-
A service is network control configurations for pods.
There are a various types you can assign as your service.- ClusterIp
- NodePort
- LoadBalancer
- ExternalName
This is a basic configuration of a
service.yaml
Lets go through the basics and see what each component do.apiVersion: v1 kind: Service metadata: name: my-service spec: type: NodePort selector: app: my-app ports: - protocol: TCP port: 80 targetPort: 8080
- Note that the
kind
must be asService
and you can specify which service it is byspec.type
.
If you do not specify, it will be of typeClusterIp
as default.
- The
spec.selector
you see above isapp:my-app
.
This is how services know which pod is assigned to this service.
You can assignselectors
to a specific pod bydeployment.yaml
. -
This is the deployments example.
apiVersion: apps/v1 kind: Deployment metadata: name: my-deployment spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-container image: my-image
You can see two instances ofapp:my-app
.
spec.selector.matchLables
=app:my-app
This means that the Deployment will identify the pod byapp:my-app
label.
spec.template.metadata.labels
=app:my-app
This menas that the template for each pods will have the labelapp:my-app
when created.
So you would need 3 instances ofapp:my-app
needed.
Two in deployment.yaml and one in service.yaml.
Note that all labels need to match if multiple is given.
- Master nodes
-
The tech industry is currently so tangles with cloud providers.
In a way that we cannot think or come up with a idea that does not use or rely on such services.
But in some cases, it is better to go locally and manage your own servers in order to cut cost. $400,000,000 Saved - NO MORE AWS
I am not in the position or experience to say such claims, but I do understand such points taken and give some cases. -
I have created a k8s cluster on aws trying to learn and deploy a microservice on my own.
But currently, the k8s requires a 2GB and a 2vCPU machine.
And k8s requires you to have 1 control plane, and 1 worker in order to get the full ordeal.
This would require me to get at least 2x t2.medium.
The monthly cost ramps up to 170$ per month if you consider all the additional cost for networking and rdb.
Which in fact, can be installed in a computer that you own in your house.
This is my bills by the way. XD
-
Now, if you do plan to go local, you should be quite careful to chose the right hardware.
It is up to you because there are so many vendors, and so many choices to be made in such occasions.
But to be simple, I will compare the t2.medium with raspberry pi 5.
A t2.medium has a max of 3.3 Ghz and 4GB of memory.
A raspbarry pi 5 has 2.4 Ghz on default and 4GB of memory.
(at the time of this writing)
A t2.medium would cost you 42.5$ per month.
A raspberry pi would cost you 73$ for purchase.
If you run your local server for more than 2 months, you can get your ROI for the initial cost.
The same can be said for GPU intensive servers, NAS servers.
It rounds up to 2 months ~ 1 year to get back your initial investment in most cases. -
The sole reason that AWS has become dominate in the first place, is because it was such a hassle in order to manage a local server.
The ISP that you have may not be sufficient to handle such traffic as in AWS.
If you have such servers managed by you or your company, it can also be a huge risk for downtime.
Like the example of the kakao datacenter on fire.
Business critical information such as money transfer can be lost in such occasions.
You should think local servers as a choice that can be made, not as a belief.