Repository di Immagini in Locale

Scenario

  1. Sviluppiamo un applicativo
  2. Lo inseriamo in un'immagine
  3. Manteniamo l'immagine in un registry locale
  4. Lanciamo un pod che carica l'immagine dal registry locale

Quando si crea un pod, l’immagine del suo container è normalmente scaricata del registry del Docker Hub.

Vogliamo creare un’immagine locale, per motivi di confidenzialità, indipendenza ed economia.

Il registry locale deve essere visibile al cluster kubernetes.

  • Servirà un cluster che lo permetta, tramite un opportuno plugin
  • Occorrerà configurazione del cluster:
    • nella costruzione
    • a runtime

Sviluppo di Applicativo

Il programma di applicativo demo è un semplicissimo web server, scritto in linguaggio Go.

Ascoterà sulla porta 8888 e, quando contattato, invierà la stringa Hello, 世界 - Ciao Mondo in qualche lingua orientale.

Scaffolding

Prepariamo la locazione in cui sviluppare l'applicativo:

mkdir -p ~/demo/hello
cd ~/demo/hello

Programma

Si chiama main.go:

vim main.go
package main

import (
  "fmt"
  "log"
  "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintln(w, "Hello, 世界")
}

func main() {
  http.HandleFunc("/", handler)
  fmt.Println("Running demo app. Press Ctrl+C to exit...")
  log.Fatal(http.ListenAndServe(":8888", nil))
}

Inserimento Applicativo in Immagine

Non è necessario installare il software del compilatore Go. Si può far compilare il programma ad un contenitore, che in seguito viene scartato.

Usiamo un build a due fasi, con un Chained Dockerfile;

  1. viene compilato l'eseguibile
  2. l'eseguibile è inserito in un'immagine vuota

Il Dockerfile è:

vim Dockerfile
FROM golang:1.17-alpine AS build

WORKDIR /src/
COPY main.go go.* /src/
ENV GO111MODULE="off"
RUN CGO_ENABLED=0 go build -o /bin/demo

FROM scratch
COPY --from=build /bin/demo /bin/demo
EXPOSE 8888
ENTRYPOINT ["/bin/demo"]

Ogni eseguibile compilato staticamente in Go include un mini sistema operativo che fornisce le funzionalità di scheduling e gestione della memoria.

Per questo nella seconda fase del build non c'è bisogno di partire da un sistema operativo di base e si può usare l'immagine di base scratch (vuota).

E' la direttiva di compilazione CGO_ENABLED=0 che disabilita il link con le librerie dinamiche di sistema e produce un eseguibile linkato staticamente.

I linguaggio Go ha molti notevoli vantaggi quando si vuole produrre software da inserire in immagini Docker.

Build dell'Immagine

Si costruisce l'immagine myhello:

docker build -t myhello .

Verificarne l'avvenuta creazione con:

docker images

Nota

Il messaggio del comando docker build avverte che build è deprecato e verrà presto sostituito da buildx.

Questo è un plugin di docker.

Per installarlo:

curl -L "https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64" -o docker-buildx
mkdir -p ~/.docker/cli-plugins
mv docker-buildx ~/.docker/cli-plugins
chmod +x ~/.docker/cli-plugins/docker-buildx
docker buildx version

Il comando di build diventa allora:

docker buildx build -t myhello .

Registry Locale

Docker Hub offre l'immagine registry per un registry locale.

Occorre quindi un piccolo refactoring del progetto.

Refactoring del Progetto

Docknew

Occorrono le seguenti attività:

  • istanziare un contenitore docker per il registry
  • creare il cluster con una opportuna patch che gli consenta di vedere il registry
  • agganciare il registry alla rete del cluster
  • creare un ConfigMap che documenti la presenza del registry

Il tutto è convenientemente raccolto in una procedura shell, copiata dalla documentazione del sito Kind, e leggermente modificata per il nostro progetto.

vim ~/registry.sh
#!/bin/sh
set -o errexit

# crea il contenitore del registry se non esiste
reg_name='kind-registry'
reg_port='5000'
if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
  docker run \
    -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \
    -v $PWD/registry:/var/lib/registry registry:2
fi

# crea un cluster con il registry locale abilitato in containerd
cat <<EOF | kind create cluster --image kindest/node:v1.24.0 --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
    endpoint = ["http://${reg_name}:5000"]
nodes:
- role: control-plane
- role: worker
- role: worker
EOF

# connette il registry alla rete del cluster se non connesso
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
  docker network connect "kind" "${reg_name}"
fi

# Documenta il local registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: local-registry-hosting
  namespace: kube-public
data:
  localRegistryHosting.v1: |
    host: "localhost:${reg_port}"
    help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF

Renderla eseguibile e lanciarla:

chmod +x ~/registry.sh
~/registry.sh

Check del cluster:

kubectl cluster-info --context kind-try
kubectl get nodes

Nostra immagine nel Registry Locale

Ora che abbiamo un registry locale, vi inseriamo l'immagine che abbiamo preparato, myhello.

Il riferimento al registry che contiene un'immagine è parte del nome dell'immagine. Aggiungiamo quindi un altro nome:

docker tag myhello:latest localhost:5000/myhello:latest

Ora possiamo compiere il push dell'immagine al registry locale:

docker push localhost:5000/myhello:latest

Creazione di Pod

Un manifest ~/scripts/localdemo.yml che compie il pull di un’immagine dal registry locale:

vim ~/scripts/localdemo.yml
kind: Pod
apiVersion: v1
metadata:
  name: myhello-pod
  labels:
    zone: prod
    version: v1
spec:
  containers:
  - name: myhello-ctr
    image: localhost:5000/myhello
    ports:
    - containerPort: 8888

Creare il pod:

kubectl apply -f ~/scripts/localdemo.yml

Check, dopo un opportuno tempo di creazione:

kubectl get pods
kubectl get events

Localpod

Il control-plane decide quale nodo avrà il pod. L’immagine del pod container è presa dal registry locale.