Minimalt livskraftig Kubernetes

Översättningen av artikeln är framtagen inför kursstarten «DevOps metoder och verktyg».

Minimalt livskraftig Kubernetes

Om du läser detta har du förmodligen hört talas om Kubernetes (och om inte, hur kom du hit?). Men vad är egentligen Kubernetes? Det är “Containerorkestrering i företagsklass”? Или «Cloud-Native Operativsystem»? Что вообще это значит?

Ärligt talat är jag inte 100 % säker. Men jag tror det är intressant att gräva ner i detaljerna och se vad som verkligen händer i Kubernetes under dess många abstraktionslager. Så för intressets skull, låt oss titta på hur en minimal “Kubernetes-kluster” faktiskt ser ut. (Det kommer att bli mycket enklare än Kubernetes The Hard Way.)

Jag antar att du har grundläggande kunskaper om Kubernetes, Linux och containrar. Allt vi kommer att diskutera här är bara avsett för utforskning/lärande, kör inget av detta i produktion!

Översikt

Kubernetes innehåller många komponenter. Enligt Wikipedia, ser arkitekturen ut på följande sätt:

Minimalt livskraftig Kubernetes

Här visas åtminstone åtta komponenter, men de flesta av dem kommer vi att ignorera. Jag vill påstå att den minimi- enhet som rimligt kan kallas Kubernetes består av tre huvudkomponenter:

  • kubelet
  • kube-apiserver (som beror på etcd — dess databas)
  • container runtime (i det här fallet Docker)

Låt oss se vad som sägs om var och en av dem i dokumentationen (ru., en.). Först kubelet:

Agenten som kör på varje nod i klustret. Den ser till att containrarna körs i podden.

Det låter ganska enkelt. Vad sägs om container runtime? Container-runtime är ett program avsett för att köra containrar.

Mycket informativt. Men om du är bekant med Docker, borde du ha en generell förståelse för vad det gör. (Detaljerna kring ansvarsfördelningen mellan container runtime och kubelet är faktiskt ganska subtila och jag kommer inte att gå in på dem här.)

API-servern

Och API-servern är en komponent av Kubernetes kontrollplan som presenterar Kubernetes API. API-servern är klientsidan av Kubernetes kontrollplan.?

Vem som helst som någonsin har gjort något med Kubernetes har behövt interagera med API:et antingen direkt eller via kubectl. Det är hjärtat av vad som gör Kubernetes till Kubernetes — hjärnan som omvandlar de mängder YAML som vi alla känner till och älskar (?) till fungerande infrastruktur. Det verkar uppenbart att API:t måste finnas med i vår minimala konfiguration.

Любому, кто когда-либо что-либо делал с Kubernetes, приходилось взаимодействовать с API либо напрямую, либо через kubectl. Это сердце того, что делает Kubernetes Kubernetes’ом — мозг, превращающий горы YAML, который мы все знаем и любим (?), в работающую инфраструктуру. Кажется очевидным, что API должен присутствовать в нашей минимальной конфигурации.

Förutsättningar

  • En virtuell eller fysisk Linux-maskin med root-åtkomst (jag använder Ubuntu 18.04 på en virtuell maskin).
  • Och det är allt!

Tråkig installation

På maskinen vi ska använda behöver vi installera Docker. (Jag kommer inte att gå in på detaljer om hur Docker och containrar fungerar; om du är intresserad finns det fantastiska artiklar). Låt oss bara installera det med apt:

$ sudo apt install docker.io
$ sudo systemctl start docker

Därefter behöver vi hämta Kubernetes-binärerna. För att starta vår "kluster" behöver vi faktiskt bara kubelet, eftersom vi kan använda kubeletför att köra andra serverkomponenter. För att interagera med vårt kluster när det väl är igång, kommer vi också att använda kubectl.

$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5

Vad händer om vi bara kör kubelet?

$ ./kubelet
F0609 04:03:29.105194    4583 server.go:254] mkdir /var/lib/kubelet: permission denied

kubelet måste köras som root. Ganska logiskt, eftersom den behöver hantera hela noden. Låt oss titta på dess alternativ:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Oj, så många alternativ! Som tur är behöver vi bara ett par av dem. Här är ett av alternativen som intresserar oss:

--pod-manifest-path string

Sökväg till katalogen som innehåller filer för statiska pods, eller sökvägen till en fil med beskrivning av statiska pods. Filer som börjar med punkter ignoreras. (URDATERAD: detta alternativ bör ställas in i konfigurationsfilen som överförs till Kubelet via alternativet —config. För mer information se. kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Detta alternativ gör att vi kan köra statiska pods — pods som inte hanteras via Kubernetes API. Statiska pods används sällan, men de är mycket praktiska för att snabbt sätta upp ett kluster, vilket är precis vad vi behöver. Vi kommer att ignorera denna varning (igen, kör inte detta i produktion!) och se om vi kan köra en pod.

Först kommer vi att skapa en katalog för statiska pods och köra kubelet:

$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods

Sedan, i ett annat terminalfönster/tmux/eller någon annanstans, kommer vi att skapa manifestet för podden:

$ cat < pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF

kubelet börjar skriva några varningar och det verkar som att inget händer. Men så är det inte! Låt oss titta på Docker:

$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   36 seconds ago      Exited (0) 36 seconds ago                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "/pause"                2 minutes ago       Up 2 minutes                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet läste manifestet för podden och gav Docker kommandot att starta ett par containrar enligt vår specifikation. (Om du är intresserad av containern “pause”, så är det en Kubernetes-hack — detaljer kan läsas i denna blogg.) Kubelet kommer att starta vår container busybox med det angivna kommandot och kommer att starta om det oändligt, så länge den statiska podden inte tas bort.

Grattis! Vi har just kommit på ett av de mest invecklade sätten att skriva ut text i terminalen!

Startar etcd

Vårt slutgiltiga mål är att starta Kubernetes API, men för det behöver vi först starta i den motsvarande Docker-bilden. Låt oss starta en minimal etcd-kluster och placera dess inställningar i katalogen pods (till exempel, pods/etcd.yaml):

apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --data-dir=/var/lib/etcd
    image: k8s.gcr.io/etcd:3.4.3-0
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
  hostNetwork: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data

Om du någonsin har arbetat med Kubernetes, bör liknande YAML-filer vara bekanta för dig. Här är det bara två saker att notera:

Vi har monterat värddatorns mapp /var/lib/etcd i podden så att etcd-data sparas efter omstart (om detta inte görs kommer klustrets tillstånd att raderas vid varje omstart av podden, vilket inte är bra ens för en minimal Kubernetes-installation).

Vi har ställt in hostNetwork: true. Denna parameter, vilket inte är förvånande, konfigurerar etcd att använda värd-nätverket istället för poddens interna nätverk (detta kommer att göra det lättare för API-servern att hitta etcd-klustret).

En enkel kontroll visar att etcd verkligen körs på localhost och sparar data på disk:

$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
    ├── snap
    │   └── db
    └── wal
        ├── 0.tmp
        └── 0000000000000000-0000000000000000.wal

Starta API-servern

Att starta Kubernetes API-servern är ännu enklare. Det enda parameter som måste anges, --etcd-servers, gör vad du förväntar dig:

apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --etcd-servers=http://127.0.0.1:2379
    image: k8s.gcr.io/kube-apiserver:v1.18.5
  hostNetwork: true

Placera den här YAML-filen i katalogen pods, och API-servern startar. Kontroll med curl visar att Kubernetes API lyssnar på port 8080 med fullständig öppen åtkomst — autentisering krävs inte!

$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}

(Återigen, kör inte detta i produktion! Jag blev lite överraskad över att standardinställningen är så osäker. Men jag antar att det är för att underlätta utveckling och testning.)

Och en trevlig överraskning, kubectl fungerar direkt utan några ytterligare inställningar!

$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.

Problemet

Men om vi gräver lite djupare verkar det som att något går snett:

$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.

De statiska poddar vi skapade är borta! Faktisk, vår kubelet-nod kan inte ens hittas:

$ ./kubectl get nodes
No resources found in default namespace.

Vad är problemet? Om ni kommer ihåg, i några stycken tillbaka körde vi kubelet med ett mycket enkelt set av kommandoradsparametrar, så kubelet vet inte hur man ska kontakta API-servern och rapportera sitt tillstånd. Genom att studera dokumentationen hittar vi den relevanta flaggan:

--kubeconfig string

Sökväg till filen kubeconfig, som anger hur man ansluter till API-servern. Närvaro av --kubeconfig aktiverar API-serverläge, frånvaro av --kubeconfig aktiverar fristående läge.

Under hela den här tiden, utan att veta om det, har vi kört kubelet i "autonomt läge". (Om vi var pedantiska skulle vi kunna betrakta kubelets autonoma läge som "minimalt livskraftig Kubernetes", men det skulle vara väldigt tråkigt). För att få den "riktiga" konfigurationen att fungera behöver vi överföra kubeconfig-filen till kubelet så att den vet hur man kommunicerar med API-servern. Lyckligtvis är detta ganska enkelt (eftersom vi inte har problem med autentisering eller certifikat):

apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s

Spara detta som kubeconfig.yaml, döda processen kubelet och starta om med nödvändiga parametrar:

$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml

(Förresten, om du försöker komma åt API:et via curl när kubelet inte kör, kommer du att upptäcka att det fortfarande fungerar! Kubelet är inte "förälder" till sina pods, liknande Docker, det liknar mer en "hanterande demon". De containrar som hanteras av kubelet kommer att fortsätta köra tills kubelet stoppar dem.)

Efter några minuter kubectl bör det visa oss pods och noder, precis som vi förväntade oss:

$ ./kubectl get pods -A
NAMESPACE     NAME                    READY   STATUS             RESTARTS   AGE
default       hello-mink8s            0/1     CrashLoopBackOff   261        21h
kube-system   etcd-mink8s             1/1     Running            0          21h
kube-system   kube-apiserver-mink8s   1/1     Running            0          21h
$ ./kubectl get nodes -owide
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
mink8s   Ready       21h   v1.18.5   10.70.10.228           Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

Låt oss verkligen gratulera oss den här gången (jag vet att jag redan har gratulerat) — vi har fått en minimal "kluster" Kubernetes som fungerar med en fullt fungerande API!

Vi startar en pod

Nu ska vi se vad API:et kan göra. Låt oss börja med en nginx-pod:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx

Här kommer vi att få ett ganska intressant felmeddelande:

$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.

Här ser vi hur fruktansvärt ofullständig vår Kubernetes-miljö är — vi har inga tjänstekonton. Låt oss försöka igen, genom att skapa ett tjänstekonto manuellt, och se vad som händer:

$ cat <<EOS | ./kubectl apply -f -
apiversion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account

Även när vi skapade servicekontot manuellt, skapas ingen autentiseringstoken. När vi fortsätter att experimentera med vår minimalistiska "kluster" kommer vi att upptäcka att de flesta användbara saker som vanligtvis sker automatiskt kommer att saknas. Kubernetes API-servern är ganska minimalistisk, och det mesta av de tunga automatiska inställningarna sker i olika kontrollanter och bakgrundsuppgifter som ännu inte körs.

Vi kan kringgå detta problem genom att ställa in alternativet automountServiceAccountToken för servicekontot (eftersom vi ändå inte behöver använda det):

$ cat <<EOS | ./kubectl apply -f -
apiversion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m

Till slut dyker podden upp! Men den kommer faktiskt inte att starta, eftersom vi inte har någon planerare (scheduler) — en annan viktig komponent av Kubernetes. Igen ser vi att Kubernetes API är förvånansvärt "dum" — när du skapar en pod i API:et registrerar det den, men försöker inte räkna ut på vilken nod den ska köras.

För att starta en pod behövs faktiskt ingen planerare. Du kan manuellt lägga till noden i manifestet i parameter nodeName:

apiversion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s

(Ersätt mink8s med nodens namn.) Efter delete och apply ser vi att nginx har startat och lyssnar på det interna IP-adressen:

$ .\/kubectl delete pod nginx
pod "nginx" raderad
$ .\/kubectl apply -f nginx.yaml
pod/nginx skapad
$ .\/kubectl get pods -owide
NAMN    KLAR   STATUS    OMSTARTNINGAR   ÅLDER   IP           NOD     NOMINERAD NOD   LÄSNINGS GATAN
nginx   1/1     Körning   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Välkommen till nginx!</title>

För att säkerställa att nätverket mellan poddar fungerar korrekt kan vi köra curl från en annan podd:

$ kat &lt;&lt;EOS | .\/kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - image: curlimages\/curl
    name: curl
    command: ["curl", "172.17.0.2"]
  nodeName: mink8s
EOS
pod\/curl skapad
$ .\/kubectl logs curl | head -6
  % Total    % Mottagen % Xferd  Genomsnittlig Hastighet   Tid    Tid     Tid  Aktuell
                                 Dload  Ladda Upp   Totalt   Tillbringad   Kvar  Hastighet
<!DOCTYPE html>
<html>
<head>
<title>Välkommen till nginx!</title>

Det är ganska intressant att gräva i denna miljö och se vad som fungerar och vad som inte gör det. Jag har upptäckt att ConfigMap och Secret fungerar som förväntat, men Service och Deployment gör det inte.

Framgång!

Detta inlägg blir långt, så jag tänker avsluta och säga att detta är en livskraftig konfiguration som kan kallas “Kubernetes”. Sammanfattningsvis: fyra binärfiler, fem kommandoradsparametrar och “bara” 45 rader YAML (inte så mycket i Kubernetes standarder) och vi har fått många saker att fungera:

  • Pods hanteras med hjälp av det vanliga Kubernetes API:et (med några hacks)
  • Det går att ladda upp offentliga containerbilder och hantera dem
  • Pods förblir aktiva och startas automatiskt om
  • Nätverket mellan pods inom en nod fungerar ganska bra
  • ConfigMap, Secret och enklast montering av lagring fungerar som det ska

Men mycket av det som gör Kubernetes verkligen användbart saknas fortfarande, till exempel:

  • Pod-schemaläggare
  • Autentisering / auktorisation
  • Flera noder
  • Tjänsternätverk
  • Klonens interna DNS
  • Kontroller för tjänstekonton, distributioner, integrationer med molnleverantörer och de flesta andra "funktioner" som Kubernetes medför

Vad har vi egentligen fått? Kubernetes API:et som fungerar på egen hand är faktiskt bara en plattform för automatisering av containrar. Det gör inte mycket — det är jobbet för olika kontroller och operatörer som använder API:et — men det ger en konsekvent miljö för automatisering.

Lär dig mer om kursen vid det kostnadsfria webbinariet.

Läsa mer:

Källa: habr.com

Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster