OCI Registry einrichten
Um eigene Images zu verwalten und zu pushen, wird ein OCI
Registry benötigt.
Im folgenden Beispiel wird die Installation über yaml Dateien durchgeführt,
die direkt über kubectl importiert werden. Dazu werden die im folgenden
aufgeführten Beispiele in Dateien gepackt und über denn Befehl kubectl apply -f $DATEINAME importiert.
Namespace
Zunächst wird ein Namespace erstellt in dem die Registry läuft. Für das Beispiel
ist das der Namespace registry.
apiVersion : v1
kind : Namespace
metadata :
name : registry
Storage
Damit die Daten der Registry auch nach einem Neustart erhalten bleiben, wird ein
Storae benötigt. Über die default Storage Klasse wird dafür ein
PersistentVolumeClaim erstellt. Dieser erstellt dann ein PersistentVolume
sobald dieses von dem Deployment angefordert wird.
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : registry
namespace : registry
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : "5Gi"
# storageClassName: longhorn
Wird eine andere als die default StorageClass gewünscht kann diese mit dem
Parameter storageClassName angegeben werden.
Secrets
Damit die Registry nicht ohne Authentifizierung läuft muss im Vorfeld eine
htpasswd Datei erstellt werden um darüber die Benutzer zu verwalten.
Zunächst wird die htpasswd Datei erstellt. Dies lässt sich am einfachsten über
den httpd OCI Container durchführen. Dieser enthält das benötigte Progamm
htpasswd. Im Beispiel wird podman genutzt. Der Befehl kann gegen docker
getauscht werden.
user@linux $ podman run --rm --entrypoint htpasswd docker.io/httpd -Bbn \
testuser testpassword > htpasswd
Über die Datei htpasswd wird anschließend ein Secret im Namespace registry erstellt.
user@linux $ kubectl create secret generic registry-secret --dry-run= client \
-o yaml --type opaque --from-file htpasswd -n registry
apiVersion: v1
data:
htpasswd: dGVzdHVzZXI6JDJ5JDA1JGVSNXliR3hoU0gxSmVtcEIxR0dZMnVwUFVJb1lFQnFTSXhaVVlEYUpYRld6SVV0UTVReDFDCgo =
kind: Secret
metadata:
creationTimestamp: null
name: registry-secret
namespace: registry
type: opaque
Deployment
Über das Deployment wird die Definition des Registry Pods durchgeführt. Dieser
wird dann über ein automatisch erstelltes ReplicaSet gestartet.
apiVersion : apps/v1
kind : Deployment
metadata :
name : registry
namespace : registry
spec :
replicas : 1
selector :
matchLabels :
app : registry
release : registry
template :
metadata :
labels :
app : registry
release : registry
spec :
containers :
env :
- name : REGISTRY_STORAGE_DELETE_ENABLED
value : "true"
- name : REGISTRY_AUTH
value : htpasswd
- name : REGISTRY_AUTH_HTPASSWD_REALM
value : Registry Realm
- name : REGISTRY_AUTH_HTPASSWD_PATH
value : /auth/htpasswd
image : registry:2
imagePullPolicy : ifNotPresent
livenessProbe :
failureThreshold : 3
httpGet :
path : /
port : 5000
scheme : HTTP
periodSeconds : 10
successThreshold : 1
timeoutSeconds : 1
name : registry
ports :
- containerPort : 5000
protocol : TCP
readinessProbe :
failureThreshold : 3
httpGet :
path : /
port : 5000
scheme : HTTP
periodSeconds : 10
successThreshold : 1
timeoutSeconds : 1
volumeMounts :
- mountPath : /auth
name : auth
readOnly : true
- mountPath : /var/lib/registry/
name : data
restartPolicy : Always
securityContext :
fsGroup : 1000
runAsUser : 1000
terminationGracePeriodSeconds : 30
volumes :
- name : auth
secret :
defaultMode : 420
items :
- key : htpasswd
path : htpasswd
secretName : registry-secret
- name : data
persistentVolumeClaim :
claimName : registry
Service
Nach dem Einlesen des Deployments existiert der Pod registry im Namespace
registry, es existiert aber noch kein Zugriff von Außen auf den Container.
Dazu wird ein Service erstellt.
apiVersion : v1
kind : Service
metadata :
name : registry
namespace : registry
spec :
ports :
- name : http-5000
port : 5000
protocol : TCP
targetPort : 5000
selector :
app : registry
release : registry
type : ClusterIP
Der Service ist in der Lage den passenden Pod auf Grund des selector zu finden
und diesem eine ClusterIP zu geben. Dies ClusterIP mit dem Port 5000 ist dann
eine Möglichkeit auf den Dienst zuzugreifen.
Allerdings nur auf einer der Cluster Nodes. Um auch von Außen auf den Dienst
zugreifen zu können macht es Sinn einen Ingress zu erstellen über den dann der
Zugriff erfolgt.
Ingress
Für den Ingress wird eine DNS Domain benötigt, die auf den Cluster zeigt. Für
das Beispiel ist es registry.internal. Um auch per https auf den Dienst
zugreifen zu können muss ein TLS Zertifikat vorhanden sein. Diese kann wie Folgt
erstellt werden:
user@linux $ openssl req -x509 -newkey rsa:4096 -keyout tls.key -nodes \
-out tls.crt -days 365 -subj '/CN=registry.internal'
Statt des selbstzertifizierten Zertifikats kann auch ein Let’s Encrypt
Zertifikat über cert-manager oder ein bestehendes Zertifikat verwendet werden.
Aus dem Zertifikat wird nun wiederum ein secret erstellt.
user@linux $ kubectl create secret tls registry-tls -n registry \
--key= tls.key --cert= tls.crt --dry-run= client -o yaml
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZHVENDQXdHZ0F3SUJBZ0lVZC9kV1Q2TEloLzBlR2JJZXNqclA1WEQyQ0hBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0hERWFNQmdHQTFVRUF3d1JjbVZuYVhOMGNua3VhVzUwWlhKdVlXd3dIaGNOTWpVd05ERTNNVEV4TmpBMApXaGNOTWpZd05ERTNNVEV4TmpBMFdqQWNNUm93R0FZRFZRUUREQkZ5WldkcGMzUnllUzVwYm5SbGNtNWhiRENDCkFpSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTFJwdUYxcW5OTXdUUDNrdy9SWFdMSWUKU1dIVXJ0U3dGMjJML0RSL0N3bWRCU0RvbjIvRHJFcTdqcmYwOUkrS21SMGJlRWNNczJUTDQvSVpIT2RYQkVZTgpMbzNJTTR5c1ZoRUh1ajRDekFQYzc5eW95U20vYjFSRVpjSElKd0ltSS93UjRrSlFhODU3eTZZVVR5enhUbFdyCm8zUXdLcHEwcnhwdDgxN083SkI0THp1V1dyTklqdWpJVHh2YXNXR1ozd0NuSmNIczRzWlZpZk1KUGZhV2RpUysKd2hTZDR6U1J1VnZFWFZMalFmYkk2bGtJVTF4THIrR2VPL0c5WVZ2TmFJeVZsT0JUd1Jqa0hDT2pOa0dpR1dKVAp1b2JoTE43KzNWaFdmYjZ3enQwV3BHM0NJTjVFemJNOUxQems3aVljdTk2RVJ3dlhvREp6eGRSbUF3WUZRY2twCjEzd1VoSGdFdkZUZDlvZk9HZ3graDd3cm1Mdk1mZitCRUk3amRNUEhST3UwSVpqSGxOQTVMRzA3ai9rbE5xWmsKMXdhZHNyUjNDZjVpM0tVbmlpMTYxQ29WMitjU1YyRTIyUFBrVnNsVklKYmJvT0VXZGc0c2ZNTHd5Y3ZSVk8xcQovVjl6MEVsajBhMllSTUdXMWI4cXIwNkpHUkRvVEFKSFNzc1R1dm5icmJ6ZzJOdEl0NFd0OFZpVXRFVjdTMkpTCnN6WmhwSTRUUlN2bHRwbUlneXVMWDUzVmNWWUhxRXBFY2xZOC9tSkdmdXJzWGxhdm9NSmlaYk5MMWpLaUJkL1kKNkRxMTJnMmMvc3BXOXlYeGJyQnlIWEZraVZpc21pOG5tTFYxQTFsSHpGc3ZTdnIzZTRpVWpLV0t3RklsQ2o4Rgorak42c2FFdjFmZHFwUldERExXMUFnTUJBQUdqVXpCUk1CMEdBMVVkRGdRV0JCVElWdGo0bDdCNVczcUw1T0NOClBrWFlNWkdpcWpBZkJnTlZIU01FR0RBV2dCVElWdGo0bDdCNVczcUw1T0NOUGtYWU1aR2lxakFQQmdOVkhSTUIKQWY4RUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElDQVFBcUtWVjFHM2JwRHFMN04rTnpNTitGK0lIMwphUVd3ZGFRSGtuN2JXYmNBamQvUEZ1YXJPdCtMaUMvUmQ5VUhLWVA3MGdHZmQ5YTFmWjBGTlZDb3RZR2ZvYnVDCjNUV0RnanRXNy8wSi9ENXFNR3REWk1UVW1NVGVheVR5VlpsdUlxcTdGMklDYjU0R0k2WnR4aWRuNUw5bkhpQU8KRGFmQnZkMTFPQ2ViZ1VsRHBVR3FoaW50ZmJsVkY1NUhwUjdiSEpqV2U1WDAva3NyZi92a0RUMEVLYTlzZkhrUwpLTTZaNm9LTVlDUEdrL1d4dzA0YkF2dzU2U0pMUnFTU0NCL1pqQVQxazhyeW1ieENMekczN1NPeDJ4NWpueEF5CkZ5RldUcExHb2hOZmRURDlxOGxlSjJYMUh5L1gwVVgyM1Vpek5US2kyRjMxallDVXQ5QkJwbEx6ekM5bW1BeEgKbm9FMnNKS3lKbXdDdTk3VGhvOUlnSHpPMVZHV3Qyb2NPQ0lNdkxqY2ZoTUpHR2F5MFVoQ3JvYk9XWlBHS3VndAoyQU85VW9WaDVWMzloVDJVSkFVZFdDN0hsYi9RdFhLL3ZyczgybW5kZDVyUXFXajVrSjFEbXRNaldFak1DcVlvCjJPd0tSc1pqT2ZzcGhDSlpIOFR5eFoxY1V3Q0laMEsrVzFxeURSWDYwUkF4M3ZtMFBueU1YNGR4aFJsMWl6VmEKZi9jc1d3K09mSnVnVzZ6Zm1VdlV4by95WG0vSElHVExaL003OUlBY3NrbnJuM0pqWTBubGlmd0h4bFV1U0F6ZwoxN2ZIcjdGcjk3NHIzYWZLdWZnSU8rR1VkZnFsQWZhWkxmcGtqdGhqZ3FEV0VBRlNaSEhxcXJuWnZva2hhZURpClIyZUZVT0JRT1czaHhhVTdZQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRQzBhYmhkYXB6VE1FejkKNU1QMFYxaXlIa2xoMUs3VXNCZHRpL3cwZndzSm5RVWc2Sjl2dzZ4S3U0NjM5UFNQaXBrZEczaEhETE5reStQeQpHUnpuVndSR0RTNk55RE9NckZZUkI3bytBc3dEM08vY3FNa3B2MjlVUkdYQnlDY0NKaVA4RWVKQ1VHdk9lOHVtCkZFOHM4VTVWcTZOME1DcWF0SzhhYmZOZXp1eVFlQzg3bGxxelNJN295RThiMnJGaG1kOEFweVhCN09MR1ZZbnoKQ1QzMmxuWWt2c0lVbmVNMGtibGJ4RjFTNDBIMnlPcFpDRk5jUzYvaG5qdnh2V0ZieldpTWxaVGdVOEVZNUJ3agpvelpCb2hsaVU3cUc0U3plL3QxWVZuMitzTTdkRnFSdHdpRGVSTTJ6UFN6ODVPNG1ITHZlaEVjTDE2QXljOFhVClpnTUdCVUhKS2RkOEZJUjRCTHhVM2ZhSHpob01mb2U4SzVpN3pIMy9nUkNPNDNURHgwVHJ0Q0dZeDVUUU9TeHQKTzQvNUpUYW1aTmNHbmJLMGR3bitZdHlsSjRvdGV0UXFGZHZuRWxkaE50ano1RmJKVlNDVzI2RGhGbllPTEh6Qwo4TW5MMFZUdGF2MWZjOUJKWTlHdG1FVEJsdFcvS3E5T2lSa1E2RXdDUjByTEU3cjUyNjI4NE5qYlNMZUZyZkZZCmxMUkZlMHRpVXJNMllhU09FMFVyNWJhWmlJTXJpMStkMVhGV0I2aEtSSEpXUFA1aVJuN3E3RjVXcjZEQ1ltV3oKUzlZeW9nWGYyT2c2dGRvTm5QN0tWdmNsOFc2d2NoMXhaSWxZckpvdko1aTFkUU5aUjh4YkwwcjY5M3VJbEl5bAppc0JTSlFvL0Jmb3plckdoTDlYM2FxVVZnd3kxdFFJREFRQUJBb0lDQUNPVTlFRXNYVGpEdm5PV3NsVVhBdzNDCkxtdm1sQUtycGpzN1VDY1VaVnRraGhYcSswTUNQeEZRTTRJeGhDaHBSL0IzTWY0bFVaNVFIaWxwN1lyczNSRnAKMlNRcWQ0eEhrd1B4MTdnU09pV0s1aDNKaGo4L0c5aHRVdnBvbk5UdUs4dXp4VUdaOWVFNEJqNi9vNnZ6L2FTawo0T3h4OFgwb3BKNWNVQk1EVWFIZXFrWWd6Y3V0UkU1QjcyUkplaEdMVlBHZWhiSmRJNDdGWVJzM1YrcXgyeVE5CklTOEt3cnhqLzBCTGxySlowdkUyeUExK09GNnNLTWloT2laZjJQdFBwRTZSMDlaNGZrMkg4OHJOams1Y2g1QWUKZmx2S3ZseE92TjRHMU84T2xRWUN5TFJEQk5PQmsyWkY2d0VIcjdhQmxVWDZKYjcrcklBMjJDNGZYbnBLQUNLMApTY0Jjd3d6dzBmQTF4a3BaTk1zSVVpLzhwLzJvUjlyY0loaUNRSTd0WFlueERRSENWZ3dVNGJITjJhakdHMUJICitZTmZsK2t4QWk1WUJja0p0bHVjQXJoWWdHUFd0b1FmWnFJU3IrNFVkWEFTZ3VBOGp5aVdHVVlBWnFuUlVrWEMKYjBseTIyR0x5dWRoN2ZJSnFmQXR3Sm5ON25icmpDZy90dHFOc1FMZWgxWGpVYUNYbExvMmVTVmFlZHNZa0dJNwpKMXVwckJiWVlqQURDa1hLaDhHYnB0cEZBZmpRekU2TjEyRTd1dVF1eGdrRXRBZnp6QlIrWTh3ejhpWkFPR043ClpWcUYwTWlRV21UWmRhM3RVSnM5QnpKMDBPZDhNbzZsVHFYTlFxOXU0TkxoOThzTTRsc0dhU0lLYmFTS09RQ2MKTkpKdXc3M3R2V0ltZlNQNm5GbVRBb0lCQVFEOFVmSTlwTU52R2picjQ3Y00vZElYRk44NWFQZ2VDd2RXRXh2KwpWUG9VeVd2THp0a3BtdWRUdWZXY25aVzVZSTZRZUVtcmk0YjNkbUMySHgvS0tTL0c0a0tvdFZncFJhd3RjSUpNCnh4ck5YdTdZSjhadkg2bG84NUxHbmlQVDR2NDRNR2dHNjFBRVREVEorakUrSStwVmpnazdEKzNXQnd5RkRSbXAKaWVoSSs2THU0VDI1U3ZEYjdaZW1OT1RXbjY1N25BOURXUzUrRUtQY00yOFJid3VTdEJiMEZNTWptUFhJM25NcwpyeDBkd2lNc0wzNStOdU9qRk14VEpocnQ5RHFESUJBUGdtdUNKTUNuVHlwakdKR01DbzJZVXpZdko3U0Z0TjMvClBCd0dQb050cjFkUW4ydnpVeHluZVBTeDZydDZYUlhOeFQyTU1tUmg2dndoTUNtdkFvSUJBUUMzQzAzTU90NFgKRTVnQkxPZzc4L2ZWb2txNURCa0tBdGlPM3lJWnFHS1BXUHRnQ2x5dUpzZ012SVI3M3hoVkJ6YTJCWkMvUkNyMApkQWRRbnZXYXRhb1YyN3YzMFBzQ0xRaFU3Q2NpdWdxRlJUN1lnV0RIQk5tbkVWeHExOWYxd2pTcHlIdVl1bUkyCk5SczZCME90RHRleld3TndvWkRCZGN1VnR5cXFReDdWRFErcHRKZ290dnhtbWNRcTN4WnhQczNuWmhVazJ1VlUKL241SnhzNWFLOHpNZHRoUWpucGdOYzVFQmhEa1pjK1dyTmM1WjBtNUsvMnh5dlFsdXF6R1AzWnJYUlFFcm1VYwpOWkQrRVJxdVVuY3BJS3pMOU8yVHFKZHoySGlQOWptSUJKOTdjVUVrNHRQcTJZYnFlWHY1SGxRczk5TGtXTFFQCm9rVHp4RUthWkFQYkFvSUJBQk5yYXkxN1pOOUVNVDN1aE1Rbk5PZzUzd1JZSStDTUVNQjdNQWhmR2ZCSG9GaEwKVDBONGZKMWhEcHBETnFiUjI2Y2EydkN0Q0hJN1ZpNHVMeEFzSTFVM1ROSVhRdlhLT3ZvMFVwTCtMLzNtZEpPRwpYQ3RwVUd5WGNwQiswMXNYZVdGeHVFL3dCSFNRT08vaXBhK1dyQTV4cEJ3N21aNkRaRjlKSnRSNW5Mc2hRdVVICnlPeGQ3RFBCRVk3bE8wZVplcUJnM252Y2ZVdU9sRTM2VjI0TUVlUDBvRitneVhRUDdick1CQ2xJUVFGdFU2K3UKNkJsbXVCWnhIMWkyOGhPbVhHcURLVGxJWEdYajFrQUtROFRrdXk4QUVPM01XYnMvWHhGN1hpcXF3YTFwV2t3eQoxcEdKODVFQ3NJM2pMVklVTXVHMEVGMko5TGE1bWlnL0liQ2NPRmtDZ2dFQWNyalgrcitPZ0tJY0sraHNhVjhBCkh0cEh3UER2SkpJaURuR1ZHc1dwZURTSjRHaStLN3hNdDRiVDloc2VVZlJpZXZURUFzeURxNUNwSVFOdjVaWWsKVXV6VVEzNnNRM0hiL2ZYQWxZaEtuYnFIcUFSMmxtWFkwRmdXTnpTdXV1NE1PYmxZbGFHRGVud1FZMjg0SUZmawpJeHBRN3Ara2JVd1oyK3pnQmJScHQ1Z25EL01MUnNGaE1pRC9qT3NqVXJqTkxsR1J3UGFjczRlSnVVMnIrV0hvClN1SFZzNitJelpJTGhDRFZKNEVUK1ZpOHo5S0hwMUlHQUErMnM0cnJUSmxyNitCN2NtK25HV1lOU04rSmZHWmQKSHQ3R3psSGxNUjJDUnU0T3FoUGxmUFdBeTBweFRPQUpkL0Q4R1NzbzR6cjZBUEVXTVYzVEJOVnNKK2dDNG1iNgo3d0tDQVFFQXoxWEdMV1pILytyblozdmVkVzh2U0U0cks0N3RXQ0dwQlJsM0xSTTBia1ZlaGJQN3JLRHg4a3MwCmtqQUtXei8rb3Vud1BqSGR0T2dBZmhVcUR6cS85Q2IzOFBOQ1Q3emM5dmlnc2FXc3ZuU05aSWNjakhFV2hpT1QKcjg1L3h6ZG82WkorOE1WQzl0Wll5YjRudmNuWkhVV2dKSmljYm1PMTAxcnJxVG1uUi8xcFZwVnF2R3FwNG5lRgprbm5mS1oydnhwZUlsQjIyQ20rNGlLNzdLekgveW1oc2xGcE5nR3FNS0F3RFlqM0NHK21hc0RPMXVXUUhSWjZWCmxQYjB0WHNOVVNYLysyb1d5U2VYWlBTbnhMd1JvUDZPdFFGTjdSR0MyekN6b05tdndnV3l5RVVrNHFtVW9vOVYKSTBrSFBkNkhFVXFkcTF3SVJ5WjBrWWFMakNJRkNBPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo =
kind: Secret
metadata:
creationTimestamp: null
name: registry-tls
namespace: registry
type: kubernetes.io/tls
Dieses secret wird in dem folgenden Ingress verwendet.
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
annotations :
# cert-manager.io/cluster-issuer: acme-production
# kubernetes.io/tls-acme: "true"
ingress.kubernetes.io/proxy-body-size : 1024m
kubernetes.io/ingress.class : nginx
nginx.ingress.kubernetes.io/proxy-body-size : 1024m
name : registry
namespace : registry
spec :
rules :
- host : registry.internal
http :
paths :
- backend :
service :
name : registry
port :
number : 5000
path : /
pathType : ImplementationSpecific
tls :
- hosts :
- registry.internal
secretName : registry-tls
Die beiden auskommentierten Einträge sind die Verwendung von cert-manager und
einem ClusterIssuer gedacht. Der ClusterIssuer muss dafür zunächst angelegt sein
und die Domain von extern durch den Zertifizierungsdienst - z. B. Let’s Encrypt
Nach dem Erstellen des Ingress ist ist die Registry über
https://registry.internal erreichbar.