<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Celso Nery</title>
    <description>The latest articles on DEV Community by Celso Nery (@celsonery).</description>
    <link>https://dev.to/celsonery</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F416745%2F74d78e44-1418-4d26-89f1-06099837bf0d.jpg</url>
      <title>DEV Community: Celso Nery</title>
      <link>https://dev.to/celsonery</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/celsonery"/>
    <language>en</language>
    <item>
      <title>On-Premise Kubernetes: Setting Up a Local Docker Image Registry</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:49:18 +0000</pubDate>
      <link>https://dev.to/celsonery/on-premise-kubernetes-setting-up-a-local-docker-image-registry-nbc</link>
      <guid>https://dev.to/celsonery/on-premise-kubernetes-setting-up-a-local-docker-image-registry-nbc</guid>
      <description>&lt;p&gt;In on-premise Kubernetes clusters, it doesn't always make sense to rely on Docker Hub (or any public cloud registry) to store application images — whether for security reasons, limited internet connectivity, or simply to keep everything within your own infrastructure. In this article, I show how to set up a &lt;strong&gt;local Docker image registry&lt;/strong&gt;, both in a simple version (for testing) and in a version with authentication (closer to a real-world scenario).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ The steps below use a registry &lt;strong&gt;without TLS&lt;/strong&gt;, suitable only for testing environments. For production, configuring a certificate and TLS connection is essential — never expose a registry without encryption in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Initial configuration
&lt;/h2&gt;

&lt;p&gt;Before starting the registry, you need to tell Docker it can trust an "insecure" registry (without TLS). This needs to be configured &lt;strong&gt;on the server running the registry and on every node in the cluster&lt;/strong&gt; that will use it.&lt;/p&gt;

&lt;p&gt;Edit the &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; file, providing the host (IP or machine name) and the registry's port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"insecure-registries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"host:port"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart the Docker service to apply the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# systemctl restart docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the registry server
&lt;/h2&gt;

&lt;p&gt;The simplest way to spin up a registry is by running the official &lt;code&gt;registry&lt;/code&gt; image as a container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;repository registry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explaining each option:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs the container in the background (daemon)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-p 5000:5000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maps port 5000 of the container to port 5000 of the host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--restart=always&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Automatically restarts the container in case of failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--name=repository&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name given to the container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;registry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Official image used to run the registry server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Testing the registry
&lt;/h2&gt;

&lt;p&gt;To confirm the registry is up, just access it through the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http://registry.zion.local:5000/v2/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty JSON response (&lt;code&gt;{}&lt;/code&gt;) already indicates the service is working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing an image to the local registry
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. List the locally available images:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Tag the image&lt;/strong&gt;, pointing it to the local registry's address — in the example, the image &lt;code&gt;oregontecnologia/myapp-api&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker tag oregontecnologia/myapp-api registry.zion.local:5000/myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Push the image to the local registry:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker push registry.zion.local:5000/myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the local registry's image in Kubernetes
&lt;/h2&gt;

&lt;p&gt;With the image published, you can already create a Deployment in the cluster pointing directly to the local registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create deploy myapp-api &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.zion.local:5000/myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And confirm the pod came up correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If Kubernetes can't pull the image (&lt;code&gt;ImagePullBackOff&lt;/code&gt; error), check whether &lt;code&gt;insecure-registries&lt;/code&gt; was configured on &lt;strong&gt;all nodes&lt;/strong&gt; of the cluster — not just on the server running the registry —, since any worker could be chosen to schedule the pod.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Leveling up: registry with authentication
&lt;/h2&gt;

&lt;p&gt;The example above is functional, but with no protection whatsoever — anyone with network access can push or pull images. For a more realistic scenario, let's add authentication via &lt;code&gt;htpasswd&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the credentials
&lt;/h3&gt;

&lt;p&gt;First, install the &lt;code&gt;htpasswd&lt;/code&gt; utility (part of the &lt;code&gt;apache2-utils&lt;/code&gt; package):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;apache2-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a folder to store the password file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/home-user/auth &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the &lt;code&gt;registry.password&lt;/code&gt; file, adding the first user with the &lt;code&gt;-c&lt;/code&gt; flag (which creates the file) and &lt;code&gt;-B&lt;/code&gt; (which uses bcrypt encryption):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-Bc&lt;/span&gt; registry.password username
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add other users afterward, &lt;strong&gt;don't use the &lt;code&gt;-c&lt;/code&gt; flag&lt;/strong&gt; again (it would overwrite the file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-B&lt;/span&gt; registry.password new_username
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the authenticated registry with Docker Compose
&lt;/h3&gt;

&lt;p&gt;Instead of a single &lt;code&gt;docker run&lt;/code&gt; command, an authenticated registry is better organized as a &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry:2&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_AUTH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;htpasswd&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_AUTH_HTPASSWD_REALM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Registry&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_AUTH_HTPASSWD_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/auth/registry.password&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./auth:/auth&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data:/data&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses the official &lt;code&gt;registry:2&lt;/code&gt; image;&lt;/li&gt;
&lt;li&gt;Enables authentication via &lt;code&gt;htpasswd&lt;/code&gt;, pointing to the password file created earlier;&lt;/li&gt;
&lt;li&gt;Persists images in a local volume (&lt;code&gt;./data&lt;/code&gt;), preventing data loss if the container is recreated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing the authenticated registry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http://server-name-or-ip:5000/v2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When trying to access it, the browser (or &lt;code&gt;docker login&lt;/code&gt;) should prompt for a username and password — confirming that authentication is active.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Having a local image registry gives more control and independence to the on-premise cluster, especially in environments without unrestricted internet access or with stricter security policies. The authenticated version via &lt;code&gt;htpasswd&lt;/code&gt; is already a good starting point for real environments — but for production, it's worth adding TLS and, depending on scale, considering more robust solutions like &lt;strong&gt;Harbor&lt;/strong&gt;, which adds features like granular access control, vulnerability scanning, and registry replication.&lt;/p&gt;

&lt;p&gt;If your cluster needs to automatically authenticate against a private registry (public or local) when pulling images, also check out the article on configuring an &lt;strong&gt;&lt;code&gt;imagePullSecret&lt;/code&gt;&lt;/strong&gt; with &lt;code&gt;regcred&lt;/code&gt; on Kubernetes.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>devops</category>
      <category>registry</category>
    </item>
    <item>
      <title>Building an On-Premise Kubernetes Cluster — Part 6: Deploying, Updating, and Scaling Your Own Application</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:30:09 +0000</pubDate>
      <link>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-6-deploying-updating-and-scaling-your-own-40f0</link>
      <guid>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-6-deploying-updating-and-scaling-your-own-40f0</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-6-deploy-atualizacao-e-escalabilidade-de-uma-588g"&gt;🇧🇷 Leia a versão em português aqui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Part 5 of this series, we validated the cluster end to end by deploying Nginx. Now let's go one step further: build a custom application's Docker image, publish it, get it running in the cluster, and explore day-to-day operations — version updates, rollback, and scalability (both manual and automatic).&lt;/p&gt;

&lt;p&gt;As an example, we used a simple REST API (&lt;code&gt;myapp.war&lt;/code&gt;), built with Spring Boot, purely for illustration — the process applies to any application packaged as a container image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the application's Docker image
&lt;/h2&gt;

&lt;p&gt;The first step is writing the application's &lt;code&gt;Dockerfile&lt;/code&gt;. In this example, a lightweight base image (&lt;code&gt;alpine&lt;/code&gt;) was used, with Java 11 installed to run the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk add vim openjdk11-jre
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; runapp.sh .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ash runapp.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image build &lt;span class="nt"&gt;-t&lt;/span&gt; oregontecnologia/myapp-api:1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Publishing the image
&lt;/h3&gt;

&lt;p&gt;Before using the image in the cluster, it needs to be available in some registry — either Docker Hub or a &lt;strong&gt;private registry&lt;/strong&gt;. If you'd rather host your own on-premise registry (recommended for corporate environments or those without internet access), check out the companion article on &lt;a href="https://dev.to/celsonery/on-premise-kubernetes-setting-up-a-local-docker-image-registry-nbc"&gt;creating a local registry server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To publish to Docker Hub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker login

username:
password:

docker push oregontecnologia/myapp-api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying the application
&lt;/h2&gt;

&lt;p&gt;With the image published, you can check the cluster's current state before proceeding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get deploy &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the Deployment directly from the command line, pointing to the published image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create deploy myapp-deploy &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oregontecnologia/myapp-api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Unlike previous examples in this series (where we used YAML files with &lt;code&gt;kubectl apply -f&lt;/code&gt;), here the Deployment is created directly via the command line with &lt;code&gt;kubectl create deploy&lt;/code&gt;. Both approaches are valid — YAML files are more suitable when you need to version and consistently reapply configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Exposing the application externally
&lt;/h3&gt;

&lt;p&gt;To make the application accessible outside the cluster, create a &lt;code&gt;LoadBalancer&lt;/code&gt;-type Service, associating a fixed external IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl expose deploy myapp-deploy &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancer &lt;span class="nt"&gt;--external-ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.0.10.100 &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In on-premise environments, the &lt;code&gt;LoadBalancer&lt;/code&gt; type usually doesn't provision a load balancer automatically (that's a native feature of cloud providers). That's why here we manually provide an external IP (&lt;code&gt;--external-ip&lt;/code&gt;) already available on the local network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Removing the application
&lt;/h3&gt;

&lt;p&gt;If you need to remove the Deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete deploy myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Updating and rolling back application versions
&lt;/h2&gt;

&lt;p&gt;One of Kubernetes' great advantages is managing updates in a controlled way, with no perceptible downtime (rolling update).&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating to a new version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;set &lt;/span&gt;image deployments/myapp-deploy myapp-api&lt;span class="o"&gt;=&lt;/span&gt;oregontecnologia/myapp-api:1.0.2 &lt;span class="nt"&gt;--record&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command updates the &lt;code&gt;myapp-api&lt;/code&gt; container's image inside the Deployment to version &lt;code&gt;1.0.2&lt;/code&gt;. The &lt;code&gt;--record&lt;/code&gt; flag records the command in the Deployment's revision history, which makes it easier to later identify what changed in each rollout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rolling back to the previous version
&lt;/h3&gt;

&lt;p&gt;If the new version causes problems, you can quickly roll back to the previous revision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout undo deployments/myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scaling the application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual scaling
&lt;/h3&gt;

&lt;p&gt;To manually adjust the number of running replicas — for example, to 3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl scale deploy myapp-deploy &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automatic scaling (HPA)
&lt;/h3&gt;

&lt;p&gt;To let Kubernetes itself automatically adjust the number of replicas based on CPU usage, you can configure a &lt;strong&gt;Horizontal Pod Autoscaler (HPA)&lt;/strong&gt;. In the example below, the cluster starts with 2 replicas and can scale up to 10, whenever average CPU utilization exceeds 75%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl autoscale deploy myapp-deploy &lt;span class="nt"&gt;--min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;--max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="nt"&gt;--cpu-percent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Adjust the &lt;code&gt;--min&lt;/code&gt;, &lt;code&gt;--max&lt;/code&gt;, and &lt;code&gt;--cpu-percent&lt;/code&gt; values according to your cluster's actual capacity and your application's expected behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To check the autoscaler's current state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get hpa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command shows, among other things, the current CPU usage relative to the configured target, and the number of replicas currently running.&lt;/p&gt;

&lt;p&gt;To remove the autoscaler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete hpa myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Now, let's put this entire manual process into a YAML file to make things easier.
&lt;/h2&gt;

&lt;p&gt;Throughout this series, we've used several &lt;code&gt;.yaml&lt;/code&gt; files to create Deployments, Services, and other objects in the cluster. In this article, let's take a step back and understand the &lt;strong&gt;basic structure&lt;/strong&gt; of these manifests — what's mandatory in every file, how to find the correct &lt;code&gt;apiVersion&lt;/code&gt;, and what the main object types we've already used look like in practice: Pod, ReplicaSet, Deployment, and Service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The minimum structure of a manifest
&lt;/h2&gt;

&lt;p&gt;Every Kubernetes object, regardless of type, is described by a YAML with four main fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;apiVersion&lt;/strong&gt;: the Kubernetes API version used to create that object (e.g., &lt;code&gt;v1&lt;/code&gt; or &lt;code&gt;apps/v1&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kind&lt;/strong&gt;: the type of object being created (&lt;code&gt;Pod&lt;/code&gt;, &lt;code&gt;Deployment&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt;, etc.);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;metadata&lt;/strong&gt;: the object's metadata — name, labels, namespace, and more;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;spec&lt;/strong&gt;: the actual specification of the object — for a Pod, for example, this is where the containers live.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to find the correct apiVersion
&lt;/h3&gt;

&lt;p&gt;Each object type (&lt;code&gt;kind&lt;/code&gt;) belongs to a specific &lt;code&gt;apiVersion&lt;/code&gt;, and this can vary between Kubernetes versions. To check which resources are available in your cluster and which API each one belongs to, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl api-resources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command lists all resources supported by the cluster, along with the corresponding API group (the &lt;code&gt;APIVERSION&lt;/code&gt; column) — it's the most reliable source for knowing which &lt;code&gt;apiVersion&lt;/code&gt; to use in each manifest, since this can change depending on the installed Kubernetes version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watch out for indentation
&lt;/h3&gt;

&lt;p&gt;YAML is a format sensitive to indentation — unlike &lt;code&gt;{}&lt;/code&gt; braces or &lt;code&gt;[]&lt;/code&gt; brackets, the data hierarchy is defined purely by spacing. Because of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never use TABs&lt;/strong&gt; to indent a YAML file — always use &lt;strong&gt;spaces&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The recommended standard (and the one used in the examples below) is &lt;strong&gt;2 spaces&lt;/strong&gt; per indentation level.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;metadata&lt;/code&gt; and &lt;code&gt;spec&lt;/code&gt; are &lt;strong&gt;mappings&lt;/strong&gt; (key-value pairs), not lists — meaning their internal fields (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;labels&lt;/code&gt;, etc.) should not start with a &lt;code&gt;-&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This last point is a common mistake: it's easy to confuse lists (which use &lt;code&gt;-&lt;/code&gt;) with simple mappings. In the Pod example below, notice that &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;labels&lt;/code&gt; sit directly under &lt;code&gt;metadata&lt;/code&gt;, with no dash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pod
&lt;/h2&gt;

&lt;p&gt;The most basic Kubernetes object is the &lt;strong&gt;Pod&lt;/strong&gt; — the smallest unit that can be created and managed in the cluster, containing one or more containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod-label&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-api&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oregontecnologia/myapp-api:1.0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the object: create vs apply
&lt;/h3&gt;

&lt;p&gt;There are two main commands for applying a manifest to the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; file.yaml
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; file.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between them matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create&lt;/code&gt;&lt;/strong&gt; only creates the object if it doesn't already exist — if the object was already created previously, the command returns an error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;apply&lt;/code&gt;&lt;/strong&gt; creates the object if it doesn't exist, or &lt;strong&gt;updates&lt;/strong&gt; the existing object if there's any change in the manifest. Because of this, &lt;code&gt;apply&lt;/code&gt; is the most commonly used command day-to-day, since it lets you reapply the same file continuously as it evolves.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Checking the creation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;-o wide&lt;/code&gt; flag shows additional information about the running pod, such as the node it's running on and its internal IP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To see all details of a pod or deployment (including recent events, which helps a lot when debugging issues):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe pod myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, for a deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe deploy myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ReplicaSet
&lt;/h2&gt;

&lt;p&gt;A single Pod doesn't automatically recover if it fails. That's where the &lt;strong&gt;ReplicaSet&lt;/strong&gt; comes in: an object responsible for ensuring a defined number of replicas of the same Pod is always running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ReplicaSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myreplicaset&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod-label&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod-label&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oregontecnologia/myapp-api:1.0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the structure is a bit more nested than the Pod's: inside &lt;code&gt;spec&lt;/code&gt;, we have the &lt;code&gt;selector&lt;/code&gt; (which defines which Pods belong to this ReplicaSet, based on labels) and the &lt;code&gt;template&lt;/code&gt; (which describes how each replica of the Pod should be created).&lt;/p&gt;

&lt;p&gt;Checking the creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get replicaset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In practice, it's rare to create ReplicaSets directly — they're normally managed automatically by a Deployment, which we'll cover next. Still, understanding this layer helps in understanding how Kubernetes guarantees Pod availability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Deployment&lt;/strong&gt; is the most common way to run applications on Kubernetes. It manages ReplicaSets automatically, adding features like rolling updates and rollback — which we already saw in practice in Part 6 of this series.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mydeploy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod-label&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod-label&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oregontecnologia/myapp-api:1.0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the structure is practically identical to the ReplicaSet's — which makes sense, since a Deployment creates and manages ReplicaSets behind the scenes. The difference lies in the &lt;code&gt;kind&lt;/code&gt; and the additional functionality the Deployment offers.&lt;/p&gt;

&lt;p&gt;Checking the creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Service
&lt;/h2&gt;

&lt;p&gt;Finally, the &lt;strong&gt;Service&lt;/strong&gt; is the object responsible for exposing Pods in a stable way, with a fixed address, regardless of how many times the Pods are recreated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myservice&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mypod-label&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;  &lt;span class="c1"&gt;# ClusterIP | NodePort | LoadBalancer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;type&lt;/code&gt; field defines how the service will be exposed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ClusterIP&lt;/strong&gt; (default): exposes the service only internally, within the cluster;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodePort&lt;/strong&gt;: exposes the service on a fixed port across all cluster nodes (we used this type in Part 5, with Nginx);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LoadBalancer&lt;/strong&gt;: requests an external load balancer — in the cloud, automatically provisioned by the provider; on-premise, it usually requires a manually configured IP or a solution like MetalLB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checking the creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object&lt;/th&gt;
&lt;th&gt;Main function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pod&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimum unit of execution (one or more containers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReplicaSet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Guarantees a fixed number of replicas of a Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manages ReplicaSets, with support for rolling updates and rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exposes Pods in a stable way, with a fixed IP and name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Understanding this hierarchy — Pod → ReplicaSet → Deployment, plus the Service as the exposure layer — is the foundation for confidently reading (and writing) any Kubernetes manifest, regardless of the application's complexity.&lt;/p&gt;

&lt;p&gt;This wraps up the more conceptual content of this series. In upcoming articles, we'll keep exploring practical, day-to-day topics of running an on-premise cluster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing the operational loop
&lt;/h2&gt;

&lt;p&gt;With deploying a custom application, controlled updates, rollback, and scalability (manual and automatic) covered, we've walked through the most common day-to-day operations of an on-premise Kubernetes cluster. From here, the cluster is ready not just to host applications, but to operate them resiliently as demand grows.&lt;/p&gt;

&lt;p&gt;This concludes the more conceptual content of this series. In the upcoming articles, we will continue exploring practical topics related to the day-to-day operations of an on-premise cluster.&lt;/p&gt;

&lt;p&gt;In the next articles of the series, we will delve into topics such as private on-premise image registries, persistent storage, and cluster observability.&lt;/p&gt;

&lt;p&gt;Related articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding and removing a worker from the cluster.&lt;/li&gt;
&lt;li&gt;Creating a registry server for your Docker images.&lt;/li&gt;
&lt;li&gt;Renewing the Kubernetes certificate.&lt;/li&gt;
&lt;li&gt;Useful add-ons for your Kubernetes cluster. &lt;/li&gt;
&lt;li&gt;Metrics Server&lt;/li&gt;
&lt;li&gt;NGINX Ingress Controller&lt;/li&gt;
&lt;li&gt;Cert-Manager&lt;/li&gt;
&lt;li&gt;Portainer&lt;/li&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building an On-Premise Kubernetes Cluster — Part 5: Deploying Your First Container</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:30:00 +0000</pubDate>
      <link>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-5-deploying-your-first-container-3goi</link>
      <guid>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-5-deploying-your-first-container-3goi</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-5-fazendo-o-deploy-do-primeiro-container-3dm1"&gt;🇧🇷 Leia a versão em português aqui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In previous parts of this series, we built the cluster from scratch: prepared the environment (Part 1), installed containerd and Kubernetes (Part 2), initialized the control-plane (Part 3), and joined the workers (Part 4). With the cluster up and all nodes in &lt;strong&gt;Ready&lt;/strong&gt; state, it's time to actually put it to work: let's deploy our first application.&lt;/p&gt;

&lt;p&gt;In this article, we'll use &lt;strong&gt;Nginx&lt;/strong&gt; as an example — a classic use case for validating that the cluster is working end to end, from pod creation to service exposure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organizing the files
&lt;/h2&gt;

&lt;p&gt;First, create a directory to organize this deployment's manifests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;nginx
&lt;span class="nb"&gt;cd &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping Kubernetes manifests organized in per-application directories is a good practice that makes maintenance and versioning (e.g., with Git) easier as the cluster grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Deployment
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Deployment&lt;/strong&gt; is the Kubernetes object responsible for managing pod replicas, ensuring the desired number of instances is always running — and handling things like rolling updates and automatic recovery in case of failure.&lt;/p&gt;

&lt;p&gt;Create the file &lt;code&gt;nginx-deployment.yaml&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.14.0&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This manifest defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2 replicas&lt;/strong&gt; of the Nginx pod (&lt;code&gt;replicas: 2&lt;/code&gt;), distributed across the available workers;&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;selector&lt;/strong&gt; that ties the Deployment to the pods via the &lt;code&gt;app: nginx&lt;/code&gt; label;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;nginx:1.14.0&lt;/code&gt; image, exposing container port &lt;code&gt;80&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Applying the Deployment
&lt;/h3&gt;

&lt;p&gt;With the file saved, apply it to the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt; will create the Deployment, and from there Kubernetes takes care of scheduling the 2 pods across the available workers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking the Deployment
&lt;/h3&gt;

&lt;p&gt;To confirm the Deployment was created and has the desired number of replicas running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get deployments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, for more complete details (events, conditions, rollout strategy, etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe deployment nginx-deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything's fine, you should see all 2 replicas ready (&lt;code&gt;2/2&lt;/code&gt;) in the availability column.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Service
&lt;/h2&gt;

&lt;p&gt;The Deployment alone isn't enough to access the application from outside the cluster — pods are ephemeral and their IPs change every time they're recreated. That's what the &lt;strong&gt;Service&lt;/strong&gt; is for: a stable access point that routes traffic to the correct pods, based on labels.&lt;/p&gt;

&lt;p&gt;Create the file &lt;code&gt;nginx-service.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-service&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the &lt;code&gt;NodePort&lt;/code&gt; type is used to expose the service on a port directly accessible through any node's IP in the cluster — a simple and practical option for on-premise environments, without relying on an external load balancer (unlike the &lt;code&gt;LoadBalancer&lt;/code&gt; type, more common in cloud providers).&lt;/p&gt;

&lt;h3&gt;
  
  
  Applying the Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking the Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command shows, among other information, the external port (&lt;code&gt;NodePort&lt;/code&gt;) automatically allocated by Kubernetes — usually in the range between &lt;code&gt;30000&lt;/code&gt; and &lt;code&gt;32767&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For more details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe service nginx-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the port in hand, Nginx can now be accessed through the IP of any node in the cluster (master or workers) on the indicated port — for example: &lt;code&gt;http://10.0.10.100:&amp;lt;nodeport&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we validated here
&lt;/h2&gt;

&lt;p&gt;If you managed to access Nginx's default page through the NodePort, this confirms the whole cluster is working correctly end to end:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The control-plane is scheduling pods normally;&lt;/li&gt;
&lt;li&gt;The pod network (CNI) is allowing communication between components;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kube-proxy&lt;/code&gt; is routing the Service's traffic to the correct pods;&lt;/li&gt;
&lt;li&gt;The workers are running containers without issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;With this, we've covered the essentials for building and validating an on-premise Kubernetes cluster from scratch: environment preparation, component installation, control-plane initialization, worker joining, and the first real application deployment.&lt;/p&gt;

&lt;p&gt;From here, natural next steps include topics like persistent storage (Persistent Volumes), Ingress Controllers, configuration and secrets management (ConfigMaps and Secrets), monitoring (Prometheus/Grafana), and cluster backup strategies (like etcd backups). This is left as a suggestion for a possible continuation of this series.&lt;/p&gt;

&lt;p&gt;If you've followed all five articles up to here, you now have a functional on-premise Kubernetes cluster, built and validated from scratch — without depending on any cloud provider.&lt;/p&gt;

&lt;p&gt;Now, in &lt;strong&gt;Part 6&lt;/strong&gt; of this series, we’ll spin up the container with our application and understand how everything works.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continued in Part 6.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>nginx</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building an On-Premise Kubernetes Cluster — Part 4: Joining the Worker Nodes</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:29:50 +0000</pubDate>
      <link>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-4-joining-the-worker-nodes-52h</link>
      <guid>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-4-joining-the-worker-nodes-52h</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-4-ingressando-os-nos-workers-nodes-3f6n"&gt;🇧🇷 Leia a versão em português aqui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In previous parts, we prepared the environment (Part 1), installed containerd and Kubernetes (Part 2), and initialized the control-plane on the master server, including the pod network (Part 3). Now let's close the basic cluster lifecycle: join the worker servers to the master and confirm everything is working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Joining the worker nodes
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;each worker server&lt;/strong&gt; you want to add to the cluster, run the &lt;code&gt;kubeadm join&lt;/code&gt; command generated at the end of the master initialization (seen in Part 3). It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubeadm &lt;span class="nb"&gt;join &lt;/span&gt;10.0.10.100:6443 &lt;span class="nt"&gt;--token&lt;/span&gt; 9e0xeu.s0if3... &lt;span class="nt"&gt;--discovery-token-ca-cert-hash&lt;/span&gt; sha256:3a328e56729515d...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command uses the &lt;strong&gt;token&lt;/strong&gt; and the &lt;strong&gt;certificate hash&lt;/strong&gt; to authenticate the worker with the control-plane and safely allow it to join the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I lost the token?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kubeadm&lt;/code&gt; tokens expire by default after 24 hours. If you didn't save the token (or it has already expired), no problem — you can generate a new join command at any time, &lt;strong&gt;from the master&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# kubeadm token create --print-join-command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new token and returns the full join command, ready to be copied and run on the workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the cluster nodes
&lt;/h2&gt;

&lt;p&gt;After running &lt;code&gt;kubeadm join&lt;/code&gt; on all the workers, go back to the &lt;strong&gt;master&lt;/strong&gt; and check whether the nodes were added correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, for a more detailed output (internal IP address, kernel version, container runtime, etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;-o wide&lt;/code&gt; flag adds extra information to the standard output.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The output should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79wupqmic77yjjr0ch0i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79wupqmic77yjjr0ch0i.png" alt="Checking cluster nodes" width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All nodes should show up with &lt;strong&gt;Ready&lt;/strong&gt; status. If any worker shows up as &lt;strong&gt;NotReady&lt;/strong&gt;, it's worth checking whether the pod network (CNI) was applied correctly and whether containerd is running without errors on that node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing with unwanted taints
&lt;/h2&gt;

&lt;p&gt;In some cases, a node may end up marked with a &lt;strong&gt;taint&lt;/strong&gt; that prevents new pods from being scheduled on it — for example, when Kubernetes detects disk pressure (&lt;code&gt;disk-pressure&lt;/code&gt;). This is common in lab environments with small disks or little free space.&lt;/p&gt;

&lt;p&gt;To check whether a specific node has this taint (in the example below, &lt;code&gt;node3&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/celso/.kube/config.bagarote describe node node3 | &lt;span class="nb"&gt;grep &lt;/span&gt;Taint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the &lt;code&gt;disk-pressure&lt;/code&gt; taint is present and you want to manually remove that restriction (being aware that the node might be low on disk space), run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/celso/.kube/config.bagarote taint node node3 node.kubernetes.io/disk-pressure:NoSchedule-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The trailing &lt;code&gt;-&lt;/code&gt; at the end of the command is what removes the taint — without it, the command would add a new taint instead of removing one.&lt;/p&gt;

&lt;p&gt;⚠️ Manually removing a &lt;code&gt;disk-pressure&lt;/code&gt; taint is a stopgap measure. The right approach is to investigate and fix the root cause (free up disk space, add more storage, etc.), since Kubernetes applies this taint as a protection against failures caused by lack of space.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Closing the loop
&lt;/h2&gt;

&lt;p&gt;With the workers joined and all nodes in &lt;strong&gt;Ready&lt;/strong&gt; state, the on-premise Kubernetes cluster is officially up: control-plane running, pod network active, and workers ready to receive workloads.&lt;/p&gt;

&lt;p&gt;This wraps up the foundation of this series — environment preparation, component installation, master initialization, and worker joining. From here, the cluster is ready for the natural next steps of any Kubernetes environment: application deployments, persistent storage configuration, ingress controllers, monitoring, and more.&lt;/p&gt;

&lt;p&gt;If you've followed this series up to this point, you now have a functional on-premise Kubernetes cluster, built from scratch, without depending on any cloud provider.&lt;/p&gt;

&lt;p&gt;Now, in &lt;strong&gt;Part 5&lt;/strong&gt; of this series, we will spin up containers and test whether everything is working and handling the workload normally.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continued in Part 5.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>kubeadm</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building an On-Premise Kubernetes Cluster — Part 3: Initializing the Master</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:29:40 +0000</pubDate>
      <link>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-3-initializing-the-master-444e</link>
      <guid>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-3-initializing-the-master-444e</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-3-inicializando-o-master-42l3"&gt;🇧🇷 Leia a versão em português aqui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In previous parts of this series, we prepared the environment (Part 1) and installed containerd, &lt;code&gt;kubelet&lt;/code&gt;, and &lt;code&gt;kubeadm&lt;/code&gt; on all nodes (Part 2). Now it's time to finally initialize the cluster: we'll create the control-plane on the master server and get it ready to accept workers and a functional pod network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing the cluster with kubeadm
&lt;/h2&gt;

&lt;p&gt;The simplest way to initialize the cluster is to run the command below &lt;strong&gt;on the master server&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# kubeadm init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, specifying important parameters manually — such as the pod network CIDR, the IP address to be advertised by the API server, and the Kubernetes version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.0.10.100 --kubernetes-version="1.31.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using a configuration file (recommended)
&lt;/h3&gt;

&lt;p&gt;More recent versions of &lt;code&gt;kubelet&lt;/code&gt; no longer accept certain options via the command line (this behavior has been deprecated). Because of this, the current recommendation is to create a configuration file for &lt;code&gt;kubeadm&lt;/code&gt;, instead of passing everything via flags.&lt;/p&gt;

&lt;p&gt;Create the &lt;code&gt;kubelet.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubeadm.k8s.io/v1beta3&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InitConfiguration&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubeadm.k8s.io/v1beta3&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterConfiguration&lt;/span&gt;
&lt;span class="na"&gt;kubernetesVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.31.1"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your desired version&lt;/span&gt;
&lt;span class="na"&gt;controlPlaneEndpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k8s-master"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubelet.config.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeletConfiguration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run the initialization pointing to that file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubeadm init &lt;span class="nt"&gt;--config&lt;/span&gt; kubelet.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What to expect from the output
&lt;/h3&gt;

&lt;p&gt;If everything goes well, the command's output will look similar to this (summarized here for readability):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[init] Using Kubernetes version: v1.31.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'

[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver-kubelet-client" certificate and key

&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

&lt;/span&gt;&lt;span class="gp"&gt;  mkdir -p $&lt;/span&gt;HOME/.kube
&lt;span class="gp"&gt;  sudo cp -i /etc/kubernetes/admin.conf $&lt;/span&gt;HOME/.kube/config
&lt;span class="gp"&gt;  sudo chown $&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;span class="go"&gt;
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.10.100:6443 --token d1dyaj.31zxywbg93s1ywjy --discovery-token-ca-cert-hash sha256:71a91721595fde66b6382908d801266602a14de8e16bdb7a3cede21509427009
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Important:&lt;/strong&gt; save the full &lt;code&gt;kubeadm join&lt;/code&gt; command (with the token and certificate hash) shown at the end of the output. You'll need it to add the workers to the cluster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting up kubectl access
&lt;/h2&gt;

&lt;p&gt;To use &lt;code&gt;kubectl&lt;/code&gt; as a regular user (without needing &lt;code&gt;sudo&lt;/code&gt; every time), copy the administrator's configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; /etc/kubernetes/admin.conf &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check the current state of the cluster, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, it's normal for some pods to show up as &lt;strong&gt;Pending&lt;/strong&gt; — that's because we haven't installed a pod network (CNI) yet, which is exactly the next step.&lt;/p&gt;

&lt;p&gt;Output example:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flouagwoxxqpvv04unuyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flouagwoxxqpvv04unuyb.png" alt="All namespaces in pending" width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing the pod network (CNI)
&lt;/h2&gt;

&lt;p&gt;Kubernetes doesn't ship with a ready-made networking solution — you need to choose and install a network plugin (CNI), responsible for allowing communication between the cluster's pods. Some popular options are &lt;strong&gt;Calico&lt;/strong&gt;, &lt;strong&gt;Canal&lt;/strong&gt;, &lt;strong&gt;Flannel&lt;/strong&gt;, and &lt;strong&gt;Weave&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use only &lt;strong&gt;one&lt;/strong&gt; of the options above — installing more than one CNI on the same cluster can cause network conflicts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Example with Calico:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/calico.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example with Flannel:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/flannel-io/flannel/v0.20.2/Documentation/kube-flannel.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example with Weave:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;kubever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl version | &lt;span class="nb"&gt;base64&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"https://cloud.weave.works/k8s/net?k8s-version=&lt;/span&gt;&lt;span class="nv"&gt;$kubever&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying the chosen manifest, run again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, within a few moments, the pods in the &lt;code&gt;kube-system&lt;/code&gt; namespace — including &lt;strong&gt;CoreDNS&lt;/strong&gt; — should switch to &lt;strong&gt;Running&lt;/strong&gt; status. This confirms the pod network is working correctly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The output must be the same as this.:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb8jnkl9ywpu2g624tt7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb8jnkl9ywpu2g624tt7r.png" alt="Output weave network" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;With the control-plane initialized, &lt;code&gt;kubectl&lt;/code&gt; configured, and the pod network installed, the cluster's master is now fully operational. Only one step remains: adding the worker servers to the cluster using the &lt;code&gt;kubeadm join&lt;/code&gt; command we saved earlier.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 4&lt;/strong&gt; of this series, we'll join the workers to the cluster and confirm all nodes are ready to receive workloads.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continued in Part 4.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>kubeadm</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building an On-Premise Kubernetes Cluster — Part 2: Installing Containerd and Kubernetes</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:29:28 +0000</pubDate>
      <link>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-2-installing-containerd-and-kubernetes-pe9</link>
      <guid>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-2-installing-containerd-and-kubernetes-pe9</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-2-instalando-containerd-e-kubernetes-ode"&gt;🇧🇷 Leia a versão em português aqui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Part 1 of this series, we prepared the environment: defined the hardware, configured &lt;code&gt;/etc/hosts&lt;/code&gt;, adjusted the firewall, and disabled SWAP on all nodes. Now that the foundation is ready, it's time to install the container runtime (&lt;strong&gt;containerd&lt;/strong&gt;) and the Kubernetes packages themselves (&lt;code&gt;kubelet&lt;/code&gt; and &lt;code&gt;kubeadm&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;All the steps below should be run on &lt;strong&gt;all servers&lt;/strong&gt; in the cluster — master and workers — unless stated otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading kernel modules
&lt;/h2&gt;

&lt;p&gt;Kubernetes, through containerd, depends on two Linux kernel modules: &lt;code&gt;overlay&lt;/code&gt; (for the layered filesystem used by containers) and &lt;code&gt;br_netfilter&lt;/code&gt; (so that bridge network traffic passes through iptables rules).&lt;/p&gt;

&lt;p&gt;For these modules to load automatically on every boot, create the file &lt;code&gt;/etc/modules-load.d/containerd.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;overlay
br_netfilter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, to load them immediately (without needing a reboot), run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;modprobe overlay
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;modprobe br_netfilter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adjusting kernel network parameters
&lt;/h2&gt;

&lt;p&gt;Create the file &lt;code&gt;/etc/sysctl.d/99-kubernetes-k8s.conf&lt;/code&gt; with the following parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;net.bridge.bridge-nf-call-iptables &lt;span class="o"&gt;=&lt;/span&gt; 1
net.ipv4.ip_forward &lt;span class="o"&gt;=&lt;/span&gt; 1
net.bridge.bridge-nf-call-ip6tables &lt;span class="o"&gt;=&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These parameters ensure that network traffic between pods and services is correctly routed and filtered by Kubernetes.&lt;/p&gt;

&lt;p&gt;To apply the settings without restarting the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;--system&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing containerd
&lt;/h2&gt;

&lt;p&gt;Containerd is the container runtime used by the cluster. In this case, we'll install it through Docker's official repository, using only the &lt;code&gt;containerd.io&lt;/code&gt; package (without installing full Docker).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Download the repository's GPG key:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/trusted.gpg.d/docker.gpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create the repository file&lt;/strong&gt; at &lt;code&gt;/etc/apt/sources.list.d/docker.list&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deb &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;arch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64] https://download.docker.com/linux/debian bullseye stable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Update the package list and install containerd:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;containerd.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Generate the default containerd config file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;containerd config default &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/containerd/config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Enable the systemd cgroup driver&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit the file &lt;code&gt;/etc/containerd/config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/containerd/config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And change the &lt;code&gt;SystemdCgroup&lt;/code&gt; option from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. This adjustment matters because the kubelet, by default, also uses &lt;code&gt;systemd&lt;/code&gt; as its cgroup driver — keeping them aligned avoids instability in container resource management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Restart containerd&lt;/strong&gt; to apply the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart containerd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing Kubernetes
&lt;/h2&gt;

&lt;p&gt;With containerd running, the next step is installing the Kubernetes components: &lt;code&gt;kubelet&lt;/code&gt; and &lt;code&gt;kubeadm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Add the Kubernetes repository GPG key:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/trusted.gpg.d/kubernetes-gpg-keyring.gpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Add the Kubernetes repository&lt;/strong&gt;, creating the file &lt;code&gt;/etc/apt/sources.list.d/kubernetes.list&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deb &lt;span class="o"&gt;[&lt;/span&gt;signed-by&lt;span class="o"&gt;=&lt;/span&gt;/etc/apt/trusted.gpg.d/kubernetes-gpg-keyring.gpg] http://pkgs.k8s.io/core:/stable:/v1.31/deb/ /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this series uses Kubernetes version &lt;strong&gt;1.31&lt;/strong&gt;. If you want to install a different version, just swap the &lt;code&gt;v1.31&lt;/code&gt; in the URL above for the desired version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;3. Update the package list:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Install Kubernetes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;kubelet kubeadm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Hold the packages' version&lt;/strong&gt;, to prevent them from being automatically upgraded (which could break the cluster's compatibility):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark hold kubelet kubeadm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Enable the kubelet to auto-start&lt;/strong&gt; on boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;kubelet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limiting image storage
&lt;/h2&gt;

&lt;p&gt;By default, the kubelet can accumulate container images over time, consuming disk space. To prevent this, you can configure image garbage collection limits in the &lt;code&gt;/var/lib/kubelet/kubeadm-flags.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vim /var/lib/kubelet/kubeadm-flags.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--image-gc-high-threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60 &lt;span class="nt"&gt;--image-gc-low-threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the kubelet to start removing unused images once the disk reaches 60% usage, stopping the cleanup once it drops to 50%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;With containerd installed and configured with the correct cgroup driver, and with &lt;code&gt;kubelet&lt;/code&gt; and &lt;code&gt;kubeadm&lt;/code&gt; ready on all nodes, we now have everything needed to initialize the cluster.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt; of this series, we'll use &lt;code&gt;kubeadm init&lt;/code&gt; to create the control-plane on the master server and then join the workers to the cluster.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continued in Part 3.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>containerd</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building an On-Premise Kubernetes Cluster — Part 1: Preparing the Environment</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:29:12 +0000</pubDate>
      <link>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-1-preparing-the-environment-m78</link>
      <guid>https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-1-preparing-the-environment-m78</guid>
      <description>&lt;p&gt;🇧🇷 &lt;a href="https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-1-preparando-o-ambiente-1fjd"&gt;Leia a versão em português aqui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the first part of a series where I'll share, step by step, how I built my own on-premise Kubernetes cluster, without relying on any cloud provider. The goal is to document the whole process — from environment preparation to a working cluster — as a reference for anyone studying the topic or looking to replicate the same setup at home or at work.&lt;/p&gt;

&lt;p&gt;I used VPS (Virtual Private Server) and VM (Virtual Machine) for this cluster. However, it can also be set up on physical machines (Bare Metal).&lt;/p&gt;

&lt;p&gt;Bye the end of this series, it will be easier to understand cloud clusters on AWS (EKS), Google (GKE) and Azure (AKS).&lt;/p&gt;

&lt;p&gt;In this first part, we'll cover everything needed &lt;strong&gt;before&lt;/strong&gt; installing any Kubernetes component: hardware requirements, basic network configuration, firewall rules, and a few mandatory operating system adjustments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;The following topology was used for this cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 servers&lt;/strong&gt; in total&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 master server&lt;/strong&gt; (control-plane): 2 CPUs (cores) and 2 GB of RAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 worker servers&lt;/strong&gt; (slaves): 1 CPU and 1 GB of RAM each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root access&lt;/strong&gt; on all machines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a minimal setup, ideal for study, lab, or testing environments. For production, resources should be scaled according to expected load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the hosts file
&lt;/h2&gt;

&lt;p&gt;Before installing anything, it's important for the machines to resolve each other by name, not just by IP. Edit the &lt;code&gt;/etc/hosts&lt;/code&gt; file on &lt;strong&gt;all servers&lt;/strong&gt; and add the corresponding entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;100&lt;/span&gt;  &lt;span class="n"&gt;master&lt;/span&gt;.&lt;span class="n"&gt;company&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;    &lt;span class="n"&gt;master&lt;/span&gt;
&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;101&lt;/span&gt;  &lt;span class="n"&gt;slave01&lt;/span&gt;.&lt;span class="n"&gt;company&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;   &lt;span class="n"&gt;slave01&lt;/span&gt;
&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;10&lt;/span&gt;.&lt;span class="m"&gt;102&lt;/span&gt;  &lt;span class="n"&gt;slave02&lt;/span&gt;.&lt;span class="n"&gt;company&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;   &lt;span class="n"&gt;slave02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that, later on, the Kubernetes components can correctly resolve node names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the Firewall
&lt;/h2&gt;

&lt;p&gt;Kubernetes depends on specific ports being open between nodes so the control-plane can communicate with the workers (and vice versa). The ports vary depending on the server's role in the cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the master server:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6443&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2379-2380&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10250&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10251&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10252&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10253&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;On the worker servers (slaves):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10251&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10255&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Make sure to open these ports in the firewall tool you're using (&lt;code&gt;ufw&lt;/code&gt;, &lt;code&gt;firewalld&lt;/code&gt;, &lt;code&gt;iptables&lt;/code&gt;, etc.), according to your chosen Linux distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disabling SWAP
&lt;/h2&gt;

&lt;p&gt;Kubernetes requires SWAP to be disabled on all cluster nodes. This is because the kubelet doesn't correctly manage memory when SWAP is active, which can cause unexpected behavior in pod scheduling.&lt;/p&gt;

&lt;p&gt;First, check if SWAP is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# swapon -s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it is, disable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# swapoff -a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this deactivation is temporary — it won't survive a reboot. To make the change permanent, edit the &lt;code&gt;/etc/fstab&lt;/code&gt; file and comment out the line referring to SWAP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;#/dev/mapper/master--vg-swap_1 none swap    sw  0   0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing communication between nodes
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;/etc/hosts&lt;/code&gt; configured, it's worth confirming the servers can communicate with each other before moving on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ping slave01&lt;/span&gt;
&lt;span class="c"&gt;# ping slave02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the pings succeed (round trip), the network environment is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restarting the servers
&lt;/h2&gt;

&lt;p&gt;Finally, to make sure all changes (especially disabling SWAP) are properly applied, restart each server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# shutdown -r now&lt;/span&gt;

or 

&lt;span class="c"&gt;# reboot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;With the environment prepared — hardware defined, network configured, firewall adjusted, and SWAP disabled — we now have the foundation needed to start installing the actual Kubernetes components.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt; of this series, we'll install the container runtime and the Kubernetes packages (&lt;code&gt;kubeadm&lt;/code&gt;, &lt;code&gt;kubelet&lt;/code&gt;, and &lt;code&gt;kubectl&lt;/code&gt;) on all nodes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continued in Part 2.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>devops</category>
    </item>
    <item>
      <title>Kubernetes On-Premise: Criando um Registro de Imagens Docker Local</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Wed, 29 Jul 2026 19:09:43 +0000</pubDate>
      <link>https://dev.to/celsonery/kubernetes-on-premise-criando-um-registro-de-imagens-docker-local-a89</link>
      <guid>https://dev.to/celsonery/kubernetes-on-premise-criando-um-registro-de-imagens-docker-local-a89</guid>
      <description>&lt;p&gt;Em clusters Kubernetes on-premise, nem sempre faz sentido depender do Docker Hub (ou de qualquer registro público na nuvem) para armazenar as imagens das aplicações — seja por questões de segurança, de conectividade limitada com a internet, ou simplesmente para manter tudo dentro da própria infraestrutura. Neste artigo, mostro como subir um &lt;strong&gt;registro de imagens Docker local&lt;/strong&gt;, tanto em uma versão simples (para testes) quanto em uma versão com autenticação (mais próxima de um cenário real).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ O passo a passo abaixo usa um registro &lt;strong&gt;sem TLS&lt;/strong&gt;, adequado apenas para ambientes de teste. Para produção, é essencial configurar um certificado e conexão TLS — nunca exponha um registro sem criptografia em produção.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configuração inicial
&lt;/h2&gt;

&lt;p&gt;Antes de subir o registro, é preciso avisar o Docker de que ele pode confiar em um registro "inseguro" (sem TLS). Essa configuração precisa ser feita &lt;strong&gt;no servidor onde o registro vai rodar e em todos os nós do cluster&lt;/strong&gt; que forem usá-lo.&lt;/p&gt;

&lt;p&gt;Edite o arquivo &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;, informando o host (IP ou nome da máquina) e a porta do registro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"insecure-registries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"host:port"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois, reinicie o serviço do Docker para aplicar a mudança:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# systemctl restart docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Criando o servidor de registro
&lt;/h2&gt;

&lt;p&gt;A forma mais simples de subir um registro é rodando a imagem oficial &lt;code&gt;registry&lt;/code&gt; como um container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;repositorio registry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explicando cada opção:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Opção&lt;/th&gt;
&lt;th&gt;Função&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Roda o container em background (daemon)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-p 5000:5000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expõe a porta 5000 do container na porta 5000 do host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--restart=always&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reinicia o container automaticamente em caso de falha&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--name=repositorio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nome dado ao container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;registry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Imagem oficial usada para rodar o servidor de registro&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Testando o registro
&lt;/h2&gt;

&lt;p&gt;Para confirmar que o registro está no ar, basta acessar pelo navegador:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://registry.zion.local:5000/v2/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma resposta vazia em JSON (&lt;code&gt;{}&lt;/code&gt;) já indica que o serviço está funcionando corretamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publicando uma imagem no registro local
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Liste as imagens disponíveis localmente:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Marque (tag) a imagem&lt;/strong&gt; apontando para o endereço do registro local — no exemplo, a imagem &lt;code&gt;oregontecnologia/myapp-api&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker tag oregontecnologia/myapp-api registry.zion.local:5000/myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Envie a imagem para o registro local:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker push registry.zion.local:5000/myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usando a imagem do registro local no Kubernetes
&lt;/h2&gt;

&lt;p&gt;Com a imagem publicada, já é possível criar um Deployment no cluster apontando diretamente para o registro local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create deploy myapp-api &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.zion.local:5000/myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E confirmar que o pod subiu corretamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Se o Kubernetes não conseguir baixar a imagem (erro de &lt;code&gt;ImagePullBackOff&lt;/code&gt;), verifique se o &lt;code&gt;insecure-registries&lt;/code&gt; foi configurado em &lt;strong&gt;todos os nós&lt;/strong&gt; do cluster — não só no servidor onde o registro está rodando —, já que qualquer worker pode ser escolhido para agendar o pod.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Subindo o nível: registro com autenticação
&lt;/h2&gt;

&lt;p&gt;O exemplo acima é funcional, mas sem nenhuma proteção — qualquer pessoa com acesso à rede pode enviar ou baixar imagens. Para um cenário mais realista, vamos adicionar autenticação via &lt;code&gt;htpasswd&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Criando as credenciais
&lt;/h3&gt;

&lt;p&gt;Primeiro, instale o utilitário &lt;code&gt;htpasswd&lt;/code&gt; (parte do pacote &lt;code&gt;apache2-utils&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;apache2-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crie uma pasta para armazenar o arquivo de senhas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/home-user/auth &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crie o arquivo &lt;code&gt;registry.password&lt;/code&gt;, adicionando o primeiro usuário com a flag &lt;code&gt;-c&lt;/code&gt; (que cria o arquivo) e &lt;code&gt;-B&lt;/code&gt; (que usa criptografia bcrypt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-Bc&lt;/span&gt; registry.password nome_usuário
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para adicionar outros usuários depois, &lt;strong&gt;não use a flag &lt;code&gt;-c&lt;/code&gt;&lt;/strong&gt; novamente (ela sobrescreveria o arquivo):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-B&lt;/span&gt; registry.password nome_do_novo_usuário
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Subindo o registro autenticado com Docker Compose
&lt;/h3&gt;

&lt;p&gt;Em vez de um único comando &lt;code&gt;docker run&lt;/code&gt;, um registro autenticado fica mais organizado como um &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry:2&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_AUTH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;htpasswd&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_AUTH_HTPASSWD_REALM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Registry&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_AUTH_HTPASSWD_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/auth/registry.password&lt;/span&gt;
      &lt;span class="na"&gt;REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./auth:/auth&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data:/data&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse arquivo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usa a imagem oficial &lt;code&gt;registry:2&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Habilita autenticação via &lt;code&gt;htpasswd&lt;/code&gt;, apontando para o arquivo de senhas criado anteriormente;&lt;/li&gt;
&lt;li&gt;Persiste as imagens em um volume local (&lt;code&gt;./data&lt;/code&gt;), evitando perda de dados caso o container seja recriado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suba o serviço:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testando o registro autenticado
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://nome-ou-ip-do-servidor:5000/v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao tentar acessar, o navegador (ou o &lt;code&gt;docker login&lt;/code&gt;) deve solicitar usuário e senha — confirmando que a autenticação está ativa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerações finais
&lt;/h2&gt;

&lt;p&gt;Ter um registro de imagens local dá mais controle e independência ao cluster on-premise, especialmente em ambientes sem acesso irrestrito à internet ou com políticas de segurança mais rígidas. A versão com autenticação via &lt;code&gt;htpasswd&lt;/code&gt; já é um bom ponto de partida para ambientes reais — mas, para produção, vale complementar com TLS e, dependendo da escala, considerar soluções mais robustas como o &lt;strong&gt;Harbor&lt;/strong&gt;, que adiciona recursos como controle de acesso granular, scanning de vulnerabilidades e replicação entre registros.&lt;/p&gt;

&lt;p&gt;Se o seu cluster precisar autenticar automaticamente contra um registro privado (público ou local) ao fazer o pull das imagens, veja também o artigo sobre como configurar um &lt;strong&gt;&lt;code&gt;imagePullSecret&lt;/code&gt;&lt;/strong&gt; com &lt;code&gt;regcred&lt;/code&gt; no Kubernetes.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>devops</category>
      <category>registry</category>
    </item>
    <item>
      <title>Montando um Cluster Kubernetes On-Premise — Parte 6: Deploy, Atualização e Escalabilidade de uma Aplicação Própria</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Wed, 29 Jul 2026 19:04:42 +0000</pubDate>
      <link>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-6-deploy-atualizacao-e-escalabilidade-de-uma-588g</link>
      <guid>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-6-deploy-atualizacao-e-escalabilidade-de-uma-588g</guid>
      <description>&lt;p&gt;🇺🇸 &lt;a href="https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-6-deploying-updating-and-scaling-your-own-40f0"&gt;English version here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Na Parte 5 desta série, validamos o cluster de ponta a ponta fazendo o deploy do Nginx. Agora vamos um passo além: construir a imagem Docker de uma aplicação própria, publicá-la, colocá-la para rodar no cluster e explorar operações do dia a dia — atualização de versão, rollback e escalabilidade (manual e automática).&lt;/p&gt;

&lt;p&gt;Como exemplo, foi utilizada uma API REST simples (&lt;code&gt;myapp.war&lt;/code&gt;), feita em Spring Boot, apenas para fins didáticos — o processo se aplica a qualquer aplicação empacotada como imagem de container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Criando a imagem Docker da aplicação
&lt;/h2&gt;

&lt;p&gt;O primeiro passo é escrever o &lt;code&gt;Dockerfile&lt;/code&gt; da aplicação. Neste exemplo, foi usada uma imagem base leve (&lt;code&gt;alpine&lt;/code&gt;), com o Java 11 instalado para rodar a aplicação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk add vim openjdk11-jre
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; runapp.sh .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ash runapp.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Construindo a imagem
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image build &lt;span class="nt"&gt;-t&lt;/span&gt; oregontecnologia/myapp-api:1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Publicando a imagem
&lt;/h3&gt;

&lt;p&gt;Antes de usar a imagem no cluster, ela precisa estar disponível em algum registro — seja o Docker Hub, seja um &lt;strong&gt;registro privado&lt;/strong&gt;. Se você optar por hospedar seu próprio registro on-premise (recomendado para ambientes corporativos ou sem acesso à internet), veja o artigo complementar sobre como &lt;a href="https://dev.to/celsonery/kubernetes-on-premise-criando-um-registro-de-imagens-docker-local-a89"&gt;criar um servidor de registro local&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Para publicar no Docker Hub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker login

username:
password:

docker push oregontecnologia/myapp-api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fazendo o deploy da aplicação
&lt;/h2&gt;

&lt;p&gt;Com a imagem publicada, é possível verificar o estado atual do cluster antes de prosseguir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get deploy &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crie o Deployment diretamente pela linha de comando, apontando para a imagem publicada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create deploy myapp-deploy &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oregontecnologia/myapp-api:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Diferente dos exemplos anteriores desta série (onde usamos arquivos YAML com &lt;code&gt;kubectl apply -f&lt;/code&gt;), aqui o Deployment é criado diretamente via linha de comando com &lt;code&gt;kubectl create deploy&lt;/code&gt;. Ambas as abordagens são válidas — arquivos YAML são mais indicados quando você precisa versionar e reaplicar configurações de forma consistente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Expondo a aplicação externamente
&lt;/h3&gt;

&lt;p&gt;Para tornar a aplicação acessível fora do cluster, crie um Service do tipo &lt;code&gt;LoadBalancer&lt;/code&gt;, associando um IP externo fixo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl expose deploy myapp-deploy &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancer &lt;span class="nt"&gt;--external-ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.0.10.100 &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Em ambientes on-premise, o tipo &lt;code&gt;LoadBalancer&lt;/code&gt; normalmente não provisiona um balanceador automaticamente (isso é um recurso nativo de provedores de nuvem). Por isso, aqui é informado manualmente um IP externo (&lt;code&gt;--external-ip&lt;/code&gt;) já disponível na rede local.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Removendo a aplicação
&lt;/h3&gt;

&lt;p&gt;Caso precise remover o Deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete deploy myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Atualizando e revertendo versões da aplicação
&lt;/h2&gt;

&lt;p&gt;Uma das grandes vantagens do Kubernetes é gerenciar atualizações de forma controlada, sem downtime perceptível (rolling update).&lt;/p&gt;

&lt;h3&gt;
  
  
  Atualizando para uma nova versão
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;set &lt;/span&gt;image deployments/myapp-deploy myapp-api&lt;span class="o"&gt;=&lt;/span&gt;oregontecnologia/myapp-api:1.0.2 &lt;span class="nt"&gt;--record&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando atualiza a imagem do container &lt;code&gt;myapp-api&lt;/code&gt; dentro do Deployment para a versão &lt;code&gt;1.0.2&lt;/code&gt;. A flag &lt;code&gt;--record&lt;/code&gt; registra o comando no histórico de revisões do Deployment, o que facilita identificar depois o que mudou em cada rollout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Revertendo para a versão anterior
&lt;/h3&gt;

&lt;p&gt;Se a nova versão apresentar problemas, é possível reverter rapidamente para a revisão anterior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout undo deployments/myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Escalando a aplicação
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Escalonamento manual
&lt;/h3&gt;

&lt;p&gt;Para ajustar manualmente o número de réplicas em execução — por exemplo, para 3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl scale deploy myapp-deploy &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Escalonamento automático (HPA)
&lt;/h3&gt;

&lt;p&gt;Para deixar o próprio Kubernetes ajustar o número de réplicas automaticamente, com base no uso de CPU, é possível configurar um &lt;strong&gt;Horizontal Pod Autoscaler (HPA)&lt;/strong&gt;. No exemplo abaixo, o cluster começa com 2 réplicas e pode escalar até 10, sempre que a utilização média de CPU ultrapassar 75%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl autoscale deploy myapp-deploy &lt;span class="nt"&gt;--min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;--max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="nt"&gt;--cpu-percent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Ajuste os valores de &lt;code&gt;--min&lt;/code&gt;, &lt;code&gt;--max&lt;/code&gt; e &lt;code&gt;--cpu-percent&lt;/code&gt; conforme a capacidade real do seu cluster e o comportamento esperado da aplicação.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para verificar o estado atual do autoscaler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get hpa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando mostra, entre outras informações, o uso atual de CPU em relação ao alvo configurado, e o número de réplicas em execução no momento.&lt;/p&gt;

&lt;p&gt;Para remover o autoscaler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete hpa myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Agora vamos colocar todo este processo manual em um arquivo YAML para facilitar as coisas.
&lt;/h2&gt;

&lt;p&gt;Ao longo desta série, usamos diversos arquivos &lt;code&gt;.yaml&lt;/code&gt; para criar Deployments, Services e outros objetos no cluster. Neste artigo, vamos dar um passo atrás e entender a &lt;strong&gt;estrutura básica&lt;/strong&gt; desses manifestos — o que é obrigatório em todo arquivo, como descobrir a &lt;code&gt;apiVersion&lt;/code&gt; correta e como ficam, na prática, os principais tipos de objeto que já usamos: Pod, ReplicaSet, Deployment e Service.&lt;/p&gt;

&lt;h2&gt;
  
  
  A estrutura mínima de um manifesto
&lt;/h2&gt;

&lt;p&gt;Todo objeto do Kubernetes, independente do tipo, é descrito por um YAML com quatro campos principais:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;apiVersion&lt;/strong&gt;: a versão da API do Kubernetes usada para criar aquele objeto (por exemplo, &lt;code&gt;v1&lt;/code&gt; ou &lt;code&gt;apps/v1&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kind&lt;/strong&gt;: o tipo de objeto que será criado (&lt;code&gt;Pod&lt;/code&gt;, &lt;code&gt;Deployment&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt;, etc.);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;metadata&lt;/strong&gt;: os metadados do objeto — nome, labels, namespace, entre outros;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;spec&lt;/strong&gt;: a especificação propriamente dita do objeto — no caso de um Pod, por exemplo, é aqui que ficam os containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Como descobrir a apiVersion correta
&lt;/h3&gt;

&lt;p&gt;Cada tipo de objeto (&lt;code&gt;kind&lt;/code&gt;) pertence a uma &lt;code&gt;apiVersion&lt;/code&gt; específica, e isso pode variar entre versões do Kubernetes. Para consultar quais recursos estão disponíveis no seu cluster e a qual API cada um pertence, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl api-resources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando lista todos os recursos suportados pelo cluster, junto com o grupo de API correspondente (coluna &lt;code&gt;APIVERSION&lt;/code&gt;) — é a fonte mais confiável para saber qual &lt;code&gt;apiVersion&lt;/code&gt; usar em cada manifesto, já que isso pode mudar conforme a versão do Kubernetes instalada.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atenção à indentação
&lt;/h3&gt;

&lt;p&gt;YAML é um formato sensível à indentação — diferente de chaves &lt;code&gt;{}&lt;/code&gt; ou colchetes &lt;code&gt;[]&lt;/code&gt;, a hierarquia dos dados é definida puramente pelos espaços. Por isso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nunca use TAB&lt;/strong&gt; para indentar um arquivo YAML — use sempre &lt;strong&gt;espaços&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;O padrão recomendado (e usado nos exemplos abaixo) é &lt;strong&gt;2 espaços&lt;/strong&gt; por nível de indentação.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;metadata&lt;/code&gt; e &lt;code&gt;spec&lt;/code&gt; são &lt;strong&gt;mapeamentos&lt;/strong&gt; (chave-valor), não listas — ou seja, seus campos internos (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;labels&lt;/code&gt;, etc.) não devem começar com &lt;code&gt;-&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esse último ponto é um erro comum: é fácil confundir listas (que usam &lt;code&gt;-&lt;/code&gt;) com mapeamentos simples. No exemplo de Pod abaixo, note que &lt;code&gt;name&lt;/code&gt; e &lt;code&gt;labels&lt;/code&gt; ficam diretamente sob &lt;code&gt;metadata&lt;/code&gt;, sem traço.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pod
&lt;/h2&gt;

&lt;p&gt;O objeto mais básico do Kubernetes é o &lt;strong&gt;Pod&lt;/strong&gt; — a menor unidade que pode ser criada e gerenciada no cluster, contendo um ou mais containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meupod&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meupod-label&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-api&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oregontecnologia/myapp-api:1.0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Criando o objeto: create vs apply
&lt;/h3&gt;

&lt;p&gt;Existem dois comandos principais para aplicar um manifesto no cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; arquivo.yaml
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; arquivo.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A diferença entre eles é importante:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create&lt;/code&gt;&lt;/strong&gt; cria o objeto apenas se ele ainda não existir — se o objeto já foi criado anteriormente, o comando retorna erro.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;apply&lt;/code&gt;&lt;/strong&gt; cria o objeto caso ele não exista, ou &lt;strong&gt;atualiza&lt;/strong&gt; o objeto existente caso haja alguma mudança no manifesto. Por isso, &lt;code&gt;apply&lt;/code&gt; é o comando mais usado no dia a dia, já que permite reaplicar o mesmo arquivo continuamente conforme ele evolui.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verificando a criação
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A flag &lt;code&gt;-o wide&lt;/code&gt; mostra informações adicionais sobre o pod em execução, como o nó onde ele está rodando e seu IP interno.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para ver todos os detalhes de um pod ou deployment (incluindo eventos recentes, o que ajuda bastante na hora de depurar problemas):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe pod myapp-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou, para um deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl describe deploy myapp-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ReplicaSet
&lt;/h2&gt;

&lt;p&gt;Um Pod sozinho não se recupera automaticamente se falhar. É aí que entra o &lt;strong&gt;ReplicaSet&lt;/strong&gt;: um objeto responsável por garantir que um número definido de réplicas do mesmo Pod esteja sempre em execução.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ReplicaSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meureplicaset&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meupod-label&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meupod-label&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oregontecnologia/myapp-api:1.0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare que a estrutura é um pouco mais aninhada que a do Pod: dentro de &lt;code&gt;spec&lt;/code&gt;, temos o &lt;code&gt;selector&lt;/code&gt; (que define quais Pods pertencem a este ReplicaSet, com base nos labels) e o &lt;code&gt;template&lt;/code&gt; (que descreve como cada réplica do Pod deve ser criada).&lt;/p&gt;

&lt;p&gt;Verificando a criação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get replicaset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Na prática, é raro criar ReplicaSets diretamente — normalmente eles são gerenciados automaticamente por um Deployment, que veremos a seguir. Ainda assim, entender essa camada ajuda a compreender como o Kubernetes garante a disponibilidade dos Pods.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;Deployment&lt;/strong&gt; é a forma mais comum de rodar aplicações no Kubernetes. Ele gerencia ReplicaSets automaticamente, adicionando recursos como rolling updates e rollback — que já vimos na prática na Parte 6 desta série.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meudeploy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meupod-label&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meupod-label&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oregontecnologia/myapp-api:1.0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note que a estrutura é praticamente idêntica à do ReplicaSet — o que faz sentido, já que um Deployment cria e gerencia ReplicaSets por trás dos panos. A diferença está no &lt;code&gt;kind&lt;/code&gt; e nas funcionalidades adicionais que o Deployment oferece.&lt;/p&gt;

&lt;p&gt;Verificando a criação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Service
&lt;/h2&gt;

&lt;p&gt;Por fim, o &lt;strong&gt;Service&lt;/strong&gt; é o objeto responsável por expor Pods de forma estável, com um endereço fixo, independente de quantas vezes os Pods sejam recriados.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meudservice&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meupod-label&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;  &lt;span class="c1"&gt;# ClusterIP | NodePort | LoadBalancer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O campo &lt;code&gt;type&lt;/code&gt; define como o serviço será exposto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ClusterIP&lt;/strong&gt; (padrão): expõe o serviço apenas internamente, dentro do cluster;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodePort&lt;/strong&gt;: expõe o serviço em uma porta fixa em todos os nós do cluster (usamos esse tipo na Parte 5, com o Nginx);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LoadBalancer&lt;/strong&gt;: solicita um balanceador de carga externo — em nuvem, provisionado automaticamente pelo provedor; em ambientes on-premise, normalmente requer um IP configurado manualmente ou uma solução como o MetalLB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verificando a criação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resumo
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Objeto&lt;/th&gt;
&lt;th&gt;Função principal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pod&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unidade mínima de execução (um ou mais containers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReplicaSet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Garante um número fixo de réplicas de um Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gerencia ReplicaSets, com suporte a rolling update e rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expõe Pods de forma estável, com IP e nome fixos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Entender essa hierarquia — Pod → ReplicaSet → Deployment, mais o Service como camada de exposição — é a base para ler (e escrever) qualquer manifesto do Kubernetes com confiança, independente da complexidade da aplicação.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fechando o ciclo operacional
&lt;/h2&gt;

&lt;p&gt;Com o deploy de uma aplicação própria, atualizações controladas, rollback e escalabilidade (manual e automática), cobrimos as operações mais comuns do dia a dia de um cluster Kubernetes on-premise. A partir daqui, o cluster está pronto não só para hospedar aplicações, mas também para operá-las de forma resiliente conforme a demanda aumenta.&lt;/p&gt;

&lt;p&gt;Isso encerra o conteúdo mais conceitual desta série. Nos próximos artigos, seguimos explorando tópicos práticos do dia a dia de um cluster on-premise.&lt;/p&gt;

&lt;p&gt;Nos próximos artigos da série, vamos aprofundar em temas como registro de imagens privado on-premise, armazenamento persistente e observabilidade do cluster.&lt;/p&gt;

&lt;p&gt;Artigos relacionados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adicionando e removendo worker ao cluster.&lt;/li&gt;
&lt;li&gt;Criando um servidor de registros para suas imagens docker.&lt;/li&gt;
&lt;li&gt;Renovando certificado do kubernetes.&lt;/li&gt;
&lt;li&gt;Addons uteis para seu kubernetes.

&lt;ul&gt;
&lt;li&gt;Metric server&lt;/li&gt;
&lt;li&gt;NGinx Ingress controller&lt;/li&gt;
&lt;li&gt;Cert-Manager&lt;/li&gt;
&lt;li&gt;Portainer&lt;/li&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Montando um Cluster Kubernetes On-Premise — Parte 5: Fazendo o Deploy do Primeiro Container</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Wed, 29 Jul 2026 19:04:34 +0000</pubDate>
      <link>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-5-fazendo-o-deploy-do-primeiro-container-3dm1</link>
      <guid>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-5-fazendo-o-deploy-do-primeiro-container-3dm1</guid>
      <description>&lt;p&gt;🇺🇸 &lt;a href="https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-5-deploying-your-first-container-3goi"&gt;English version here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nas partes anteriores desta série, construímos o cluster do zero: preparamos o ambiente (Parte 1), instalamos containerd e Kubernetes (Parte 2), inicializamos o control-plane (Parte 3) e ingressamos os workers (Parte 4). Com o cluster de pé e todos os nós no estado &lt;strong&gt;Ready&lt;/strong&gt;, chegou a hora de colocá-lo para trabalhar de verdade: vamos fazer o deploy da nossa primeira aplicação.&lt;/p&gt;

&lt;p&gt;Neste artigo, vamos usar o &lt;strong&gt;Nginx&lt;/strong&gt; como exemplo — um caso clássico para validar que o cluster está funcionando de ponta a ponta, desde a criação dos pods até a exposição do serviço.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organizando os arquivos
&lt;/h2&gt;

&lt;p&gt;Antes de tudo, crie um diretório para organizar os manifestos desse deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;nginx
&lt;span class="nb"&gt;cd &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Costumo deixar os manifestos do Kubernetes organizados em diretórios por aplicação, considero uma boa prática que facilita a manutenção e o versionamento (por exemplo, com Git) conforme o cluster cresce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Criando o Deployment
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;Deployment&lt;/strong&gt; é o objeto do Kubernetes responsável por gerenciar réplicas de pods, garantindo que o número desejado de instâncias esteja sempre em execução — e cuidando de coisas como rolling updates e recuperação automática em caso de falha.&lt;/p&gt;

&lt;p&gt;Crie o arquivo &lt;code&gt;nginx-deployment.yaml&lt;/code&gt; com o seguinte conteúdo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.14.0&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse manifesto define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2 réplicas&lt;/strong&gt; do pod Nginx (&lt;code&gt;replicas: 2&lt;/code&gt;), distribuídas entre os workers disponíveis;&lt;/li&gt;
&lt;li&gt;Um &lt;strong&gt;selector&lt;/strong&gt; que associa o Deployment aos pods através do label &lt;code&gt;app: nginx&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;A imagem &lt;code&gt;nginx:1.14.0&lt;/code&gt;, expondo a porta &lt;code&gt;80&lt;/code&gt; do container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Aplicando o Deployment
&lt;/h3&gt;

&lt;p&gt;Com o arquivo salvo, aplique-o no cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;kubectl&lt;/code&gt; vai criar o Deployment e, a partir dele, o Kubernetes se encarrega de agendar os 2 pods nos workers disponíveis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verificando o Deployment
&lt;/h3&gt;

&lt;p&gt;Para conferir se o Deployment foi criado e está com as réplicas desejadas em execução:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get deployments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E, para detalhes mais completos (eventos, condições, estratégia de rollout, etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe deployment nginx-deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se tudo estiver certo, você deve ver as 3 réplicas prontas (&lt;code&gt;2/2&lt;/code&gt;) na coluna de disponibilidade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Criando o Service
&lt;/h2&gt;

&lt;p&gt;Só o Deployment não é suficiente para acessar a aplicação de fora do cluster — pods são efêmeros e seus IPs mudam a cada recriação. É para isso que existe o &lt;strong&gt;Service&lt;/strong&gt;: um ponto de acesso estável que direciona tráfego para os pods corretos, com base nos labels.&lt;/p&gt;

&lt;p&gt;Crie o arquivo &lt;code&gt;nginx-service.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-service&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui, o tipo &lt;code&gt;NodePort&lt;/code&gt; é usado para expor o serviço em uma porta acessível diretamente pelo IP de qualquer nó do cluster — uma opção simples e prática para ambientes on-premise, sem depender de um load balancer externo (diferente do tipo &lt;code&gt;LoadBalancer&lt;/code&gt;, mais comum em provedores de nuvem).&lt;/p&gt;

&lt;h3&gt;
  
  
  Aplicando o Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verificando o Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando mostra, entre outras informações, a porta externa (&lt;code&gt;NodePort&lt;/code&gt;) que foi alocada automaticamente pelo Kubernetes — normalmente na faixa entre &lt;code&gt;30000&lt;/code&gt; e &lt;code&gt;32767&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para mais detalhes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe service nginx-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com a porta em mãos, o Nginx já pode ser acessado através do IP de qualquer nó do cluster (master ou workers) na porta indicada — por exemplo: &lt;code&gt;http://10.0.10.100:&amp;lt;porta-nodeport&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que validamos aqui
&lt;/h2&gt;

&lt;p&gt;Se você conseguiu acessar a página padrão do Nginx através do NodePort, isso confirma que todo o cluster está funcionando corretamente de ponta a ponta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O control-plane está agendando pods normalmente;&lt;/li&gt;
&lt;li&gt;A rede de pods (CNI) está permitindo a comunicação entre os componentes;&lt;/li&gt;
&lt;li&gt;O &lt;code&gt;kube-proxy&lt;/code&gt; está roteando o tráfego do Service para os pods corretos;&lt;/li&gt;
&lt;li&gt;Os workers estão executando containers sem problemas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Considerações finais
&lt;/h2&gt;

&lt;p&gt;Com isso, fechamos o essencial para montar e validar um cluster Kubernetes on-premise do zero: preparação do ambiente, instalação dos componentes, inicialização do control-plane, ingresso dos workers e o primeiro deploy de uma aplicação real.&lt;/p&gt;

&lt;p&gt;A partir daqui, os próximos passos naturais incluem temas como armazenamento persistente (Persistent Volumes), Ingress Controllers, gerenciamento de configurações e segredos (ConfigMaps e Secrets), monitoramento (Prometheus/Grafana) e estratégias de backup do cluster (como o etcd). Fica a sugestão para uma possível continuação desta série.&lt;/p&gt;

&lt;p&gt;Se você seguiu os cinco artigos até aqui, já tem em mãos um cluster Kubernetes on-premise funcional, construído e validado do zero — sem depender de nenhum provedor de nuvem.&lt;/p&gt;

&lt;p&gt;Agora na &lt;strong&gt;Parte 6&lt;/strong&gt; desta série, vamos subir nosso container com nossa aplicação e endentender como tudo funciona.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continua na Parte 6.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>nginx</category>
      <category>devops</category>
    </item>
    <item>
      <title>Montando um Cluster Kubernetes On-Premise — Parte 4: Ingressando os Nós Workers Nodes</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Wed, 29 Jul 2026 19:04:25 +0000</pubDate>
      <link>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-4-ingressando-os-nos-workers-nodes-3f6n</link>
      <guid>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-4-ingressando-os-nos-workers-nodes-3f6n</guid>
      <description>&lt;p&gt;🇺🇸 &lt;a href="https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-4-joining-the-worker-nodes-52h"&gt;English version here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nas partes anteriores, preparamos o ambiente (Parte 1), instalamos o containerd e o Kubernetes (Parte 2) e inicializamos o control-plane no servidor master, incluindo a rede de pods (Parte 3). Agora vamos fechar o ciclo básico do cluster: unir os servidores workers ao master e confirmar que tudo está funcionando corretamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingressando os nós workers nodes
&lt;/h2&gt;

&lt;p&gt;Em &lt;strong&gt;cada servidor worker&lt;/strong&gt; que você deseja adicionar ao cluster, execute o comando &lt;code&gt;kubeadm join&lt;/code&gt; gerado no final da inicialização do master (Parte 3). Ele tem o seguinte formato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubeadm &lt;span class="nb"&gt;join &lt;/span&gt;10.0.10.100:6443 &lt;span class="nt"&gt;--token&lt;/span&gt; 9e0xeu.s0if3... &lt;span class="nt"&gt;--discovery-token-ca-cert-hash&lt;/span&gt; sha256:3a328e56729515d...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando usa o &lt;strong&gt;token&lt;/strong&gt; e o &lt;strong&gt;hash do certificado&lt;/strong&gt; para autenticar o worker junto ao control-plane e permitir que ele se junte ao cluster com segurança.&lt;/p&gt;

&lt;h3&gt;
  
  
  E se perdeu o token?
&lt;/h3&gt;

&lt;p&gt;Tokens do &lt;code&gt;kubeadm&lt;/code&gt; expiram por padrão após 24 horas. Se você não guardou o token (ou ele já expirou), não tem problema — é possível gerar um novo comando de join a qualquer momento, direto &lt;strong&gt;no master&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# kubeadm token create --print-join-command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando cria um novo token e já retorna o comando completo, pronto para ser copiado e executado nos workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verificando os nós do cluster
&lt;/h2&gt;

&lt;p&gt;Depois de rodar o &lt;code&gt;kubeadm join&lt;/code&gt; em todos os workers, volte para o &lt;strong&gt;master&lt;/strong&gt; e confira se os nós foram adicionados corretamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou, para uma saída com mais detalhes (endereço IP interno, versão do kernel, container runtime, etc.) acrescente &lt;strong&gt;-o wide&lt;/strong&gt; ao final do comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A opção &lt;code&gt;-o wide&lt;/code&gt; acrescenta informações extras à saída padrão.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A saída deve ser parecida com esta:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fchecando_nos_do_cluster.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/imgs%2Fchecando_nos_do_cluster.png" alt="Verificando os nós do cluster" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Todos os nós devem aparecer com o status &lt;strong&gt;Ready&lt;/strong&gt;. Se algum worker aparecer como &lt;strong&gt;NotReady&lt;/strong&gt;, vale conferir se a rede de pods (CNI) foi aplicada corretamente e se o containerd está rodando sem erros nesse nó.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lidando com taints indesejados
&lt;/h2&gt;

&lt;p&gt;Em alguns casos, um nó pode ficar marcado com um &lt;strong&gt;taint&lt;/strong&gt; que impede o agendamento de novos pods nele — por exemplo, quando o Kubernetes detecta pressão de disco (&lt;code&gt;disk-pressure&lt;/code&gt;). Isso é comum em ambientes de laboratório com discos pequenos ou pouco espaço livre.&lt;/p&gt;

&lt;p&gt;Para verificar se um nó específico está com esse taint (no exemplo abaixo, o &lt;code&gt;node3&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/celso/.kube/config.bagarote describe node node3 | &lt;span class="nb"&gt;grep &lt;/span&gt;Taint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o taint &lt;code&gt;disk-pressure&lt;/code&gt; estiver presente e você quiser remover essa restrição manualmente (ciente de que o nó pode estar com pouco espaço em disco), execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/celso/.kube/config.bagarote taint node node3 node.kubernetes.io/disk-pressure:NoSchedule-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O sinal de &lt;code&gt;-&lt;/code&gt; no final do comando é o que remove o taint — sem ele, o comando adicionaria um novo taint em vez de remover.&lt;/p&gt;

&lt;p&gt;⚠️ Remover um taint de &lt;code&gt;disk-pressure&lt;/code&gt; manualmente é uma medida paliativa. O ideal é investigar e resolver a causa raiz (liberar espaço em disco, aumentar o armazenamento, etc.), já que o Kubernetes aplica esse taint como proteção contra falhas por falta de espaço.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fechando o ciclo
&lt;/h2&gt;

&lt;p&gt;Com os workers ingressados e todos os nós no estado &lt;strong&gt;Ready&lt;/strong&gt;, o cluster Kubernetes on-premise está oficialmente de pé: control-plane funcionando, rede de pods ativa e workers prontos para receber cargas de trabalho.&lt;/p&gt;

&lt;p&gt;Isso encerra a base desta série — preparação do ambiente, instalação dos componentes, inicialização do master e ingresso dos workers. A partir daqui, o cluster está pronto para os próximos passos naturais de qualquer ambiente Kubernetes: deploy de aplicações, configuração de armazenamento persistente, ingress controllers, monitoramento, entre outros.&lt;/p&gt;

&lt;p&gt;Se você seguiu essa série até aqui, já tem em mãos um cluster Kubernetes on-premise funcional, construído do zero, sem depender de nenhum provedor de nuvem.&lt;/p&gt;

&lt;p&gt;Agora na &lt;strong&gt;Parte 5&lt;/strong&gt; desta série, vamos subir containers e testar se tudo está funcioando e recebendo carga de trabalho normalmento.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continua na Parte 5.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>kubeadm</category>
      <category>devops</category>
    </item>
    <item>
      <title>Montando um Cluster Kubernetes On-Premise — Parte 3: Inicializando o Master</title>
      <dc:creator>Celso Nery</dc:creator>
      <pubDate>Wed, 29 Jul 2026 19:04:16 +0000</pubDate>
      <link>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-3-inicializando-o-master-42l3</link>
      <guid>https://dev.to/celsonery/montando-um-cluster-kubernetes-on-premise-parte-3-inicializando-o-master-42l3</guid>
      <description>&lt;p&gt;🇺🇸 &lt;a href="https://dev.to/celsonery/building-an-on-premise-kubernetes-cluster-part-3-initializing-the-master-444e"&gt;English version here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nas partes anteriores desta série, preparamos o ambiente (Parte 1) e instalamos o containerd, o &lt;code&gt;kubelet&lt;/code&gt; e o &lt;code&gt;kubeadm&lt;/code&gt; em todos os nós (Parte 2). Chegou o momento de, finalmente, inicializar o cluster: vamos criar o control-plane no servidor master e deixá-lo pronto para receber os workers e uma rede de pods funcional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inicializando o cluster com kubeadm
&lt;/h2&gt;

&lt;p&gt;A forma mais simples de inicializar o cluster é rodando o comando abaixo &lt;strong&gt;no servidor master&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# kubeadm init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou, especificando parâmetros importantes manualmente — como o CIDR da rede de pods, o endereço IP a ser anunciado pelo API server e a versão do Kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.0.10.100 --kubernetes-version="1.31.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usando um arquivo de configuração (recomendado)
&lt;/h3&gt;

&lt;p&gt;Versões mais recentes do &lt;code&gt;kubelet&lt;/code&gt; não aceitam mais determinadas opções via linha de comando (esse comportamento foi descontinuado). Por isso, o recomendado atualmente é criar um arquivo de configuração para o &lt;code&gt;kubeadm&lt;/code&gt;, em vez de passar tudo por flags.&lt;/p&gt;

&lt;p&gt;Crie o arquivo &lt;code&gt;kubelet.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubeadm.k8s.io/v1beta3&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;InitConfiguration&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubeadm.k8s.io/v1beta3&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterConfiguration&lt;/span&gt;
&lt;span class="na"&gt;kubernetesVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.31.1"&lt;/span&gt; &lt;span class="c1"&gt;# Substitua pela versão desejada&lt;/span&gt;
&lt;span class="na"&gt;controlPlaneEndpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k8s-master"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubelet.config.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeletConfiguration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E execute a inicialização apontando para esse arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubeadm init &lt;span class="nt"&gt;--config&lt;/span&gt; kubelet.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  O que esperar da saída
&lt;/h3&gt;

&lt;p&gt;Se tudo correr bem, a saída do comando será parecida com esta (resumida aqui para facilitar a leitura):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[init] Using Kubernetes version: v1.31.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'

[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver-kubelet-client" certificate and key

&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

&lt;/span&gt;&lt;span class="gp"&gt;  mkdir -p $&lt;/span&gt;HOME/.kube
&lt;span class="gp"&gt;  sudo cp -i /etc/kubernetes/admin.conf $&lt;/span&gt;HOME/.kube/config
&lt;span class="gp"&gt;  sudo chown $&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;span class="go"&gt;
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.10.100:6443 --token d1dyaj.31zxywbg93s1ywjy --discovery-token-ca-cert-hash sha256:71a91721595fde66b6382908d801266602a14de8e16bdb7a3cede21509427009
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Importante:&lt;/strong&gt; guarde o comando &lt;code&gt;kubeadm join&lt;/code&gt; completo (com o token e o hash do certificado) exibido no final da saída. Ele será necessário para adicionar os workers ao cluster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configurando o acesso via kubectl
&lt;/h2&gt;

&lt;p&gt;Para conseguir usar o &lt;code&gt;kubectl&lt;/code&gt; como usuário comum (sem precisar de &lt;code&gt;sudo&lt;/code&gt; toda vez), copie o arquivo de configuração do administrador:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; /etc/kubernetes/admin.conf &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para verificar o estado atual do cluster, rode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse momento, é normal que alguns pods apareçam com status &lt;strong&gt;Pending&lt;/strong&gt; — isso acontece porque ainda não instalamos uma rede de pods (CNI), e é exatamente esse o próximo passo.&lt;/p&gt;

&lt;p&gt;Exemplo de saida:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3f8ne6qrszvrin84cg8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3f8ne6qrszvrin84cg8.png" alt="Todos os namespaces pendentes" width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalando a rede de pods (CNI)
&lt;/h2&gt;

&lt;p&gt;O Kubernetes não vem com uma solução de rede pronta — é preciso escolher e instalar um plugin de rede (CNI), responsável por permitir a comunicação entre os pods do cluster. Algumas opções populares são &lt;strong&gt;Calico&lt;/strong&gt;, &lt;strong&gt;Canal&lt;/strong&gt;, &lt;strong&gt;Flannel&lt;/strong&gt; e &lt;strong&gt;Weave&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use apenas &lt;strong&gt;uma&lt;/strong&gt; das opções acima — instalar mais de um CNI no mesmo cluster pode causar conflitos de rede.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Exemplo com Calico:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/calico.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exemplo com Flannel:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/flannel-io/flannel/v0.20.2/Documentation/kube-flannel.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exemplo com Weave:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;kubever&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl version | &lt;span class="nb"&gt;base64&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"https://cloud.weave.works/k8s/net?k8s-version=&lt;/span&gt;&lt;span class="nv"&gt;$kubever&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de aplicar o manifesto escolhido, rode novamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E, em alguns instantes, os pods do namespace &lt;code&gt;kube-system&lt;/code&gt; — incluindo o &lt;strong&gt;CoreDNS&lt;/strong&gt; — devem passar para o status &lt;strong&gt;Running&lt;/strong&gt;. Isso confirma que a rede de pods está funcionando corretamente.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A saída deve ser igual a essa:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi0d3b2rhrrw6u5d7jmji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi0d3b2rhrrw6u5d7jmji.png" alt="Resposta rede wave" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Próximos passos
&lt;/h2&gt;

&lt;p&gt;Com o control-plane inicializado, o &lt;code&gt;kubectl&lt;/code&gt; configurado e a rede de pods instalada, o master do cluster já está totalmente operacional. Falta apenas um passo: adicionar os servidores workers/nodes ao cluster usando o comando &lt;code&gt;kubeadm join&lt;/code&gt; que guardamos anteriormente.&lt;/p&gt;

&lt;p&gt;Na &lt;strong&gt;Parte 4&lt;/strong&gt; desta série, vamos unir os workers/nodes ao cluster e validar que todos os nós estão prontos para receber cargas de trabalho.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continua na Parte 4.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>kubeadm</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
