Add support for zuul-registry

Change-Id: Ie206f8f7c48da3cfaabb33d16f2a339b35db4e5e
This commit is contained in:
James E. Blair 2021-04-10 18:21:39 -07:00
parent 6e0fb7fe1b
commit d87ea64c06
8 changed files with 260 additions and 27 deletions

View File

@ -13,8 +13,6 @@ spec:
scheduler: scheduler:
config: config:
secretName: zuul-yaml-conf secretName: zuul-yaml-conf
registry:
count: 1
launcher: launcher:
config: config:
secretName: nodepool-yaml-conf secretName: nodepool-yaml-conf

View File

@ -192,6 +192,33 @@ static HTML/Javascript sites). If you enable this, the operator will
configure a ``zuul-preview`` service to which you may route an Ingress configure a ``zuul-preview`` service to which you may route an Ingress
or LoadBalancer. or LoadBalancer.
Zuul Registry
-------------
The operator has optional support for deploying a zuul-registry
service. This is an experimental add-on for Zuul to act as an
intermediate registry for the container image jobs in `zuul-jobs`.
If you enable this, the operator will, by default, configure a
``zuul-registry`` service in a manner appropriate for access from
within the cluster only. If you need to access the registry from
outside the cluster, you will need to additionally add an Ingress or
LoadBalancer, as well as provide TLS certs with the appropriate
hostname. Currently, zuul-registry performs its own TLS termination.
If you usue this, you will also need to provide a ``registry.yaml``
config file in a secret. You only need to provide the ``users`` and,
if you are accessing the registry outside the cluster, the
``public-url`` setting (omit it if you are accessing it from within
the cluster only).
.. code-block:: yaml
registry:
users:
- name: testuser
pass: testpass
access: write
Specification Reference Specification Reference
----------------------- -----------------------
@ -481,3 +508,36 @@ verbatim):
:default: 0 :default: 0
How many Zuul Preview servers to manage. How many Zuul Preview servers to manage.
.. attr:: registry
.. attr:: count
:default: 0
How many Zuul Registry servers to manage.
.. attr:: volumeSize
:default: 80Gi
The requested size of the registry storage volume.
.. attr:: tls
.. attr:: secretName
The name of a secret containing a TLS client certificate
and key for Zuul Registry. This should be (or the format
should match) a standard Kubernetes TLS secret.
If you omit this, the operator will create a secret for
you.
.. attr:: config
.. attr:: secretName
The name of a secret containing a registry
configuration file. The key in the secret should be
``registry.yaml``. Only provide the ``users`` and, if
exposing the registry outside the cluster, the
``public-url`` entries.

View File

@ -37,6 +37,8 @@
secretName: nodepool-kube-config secretName: nodepool-kube-config
registry: registry:
count: 1 count: 1
config:
secretName: zuul-registry-conf
preview: preview:
count: 1 count: 1
@ -45,7 +47,3 @@
- name: Test the cert-manager - name: Test the cert-manager
include_tasks: ./tasks/test_cert_manager.yaml include_tasks: ./tasks/test_cert_manager.yaml
# TODO: implement
# - name: Test the registry
# include_tasks: ./tasks/test_registry.yaml

View File

@ -68,3 +68,12 @@
- name: nodepool-kube-config - name: nodepool-kube-config
data: data:
kube.config: "{{ _kube_config.stdout }}" kube.config: "{{ _kube_config.stdout }}"
- name: zuul-registry-conf
data:
registry.yaml: |
registry:
users:
- name: testuser
pass: testpass
access: write

View File

@ -1,21 +1,23 @@
- name: Get registry service ip - k8s:
command: kubectl get svc registry -o "jsonpath={.spec.clusterIP}" namespace: default
register: _registry_ip definition:
apiVersion: batch/v1
- name: Add registry to /etc/hosts kind: Job
become: yes metadata:
lineinfile: name: test-registry
path: /etc/hosts spec:
regexp: "^.* registry$" template:
line: "{{ _registry_ip.stdout_lines[0] }} registry" spec:
containers:
- name: Get registry password - name: test-registry
command: kubectl get secret zuul-registry-user-rw -o "jsonpath={.data.password}" image: quay.io/containers/podman:latest
register: _registry_password command: ['podman', 'login', '--tls-verify=false', 'https://zuul-registry/', '-u', 'testuser', '-p', 'testpass']
securityContext:
- name: Test registry login privileged: true
command: > restartPolicy: Never
podman login backoffLimit: 4
--tls-verify=false registry:9000 wait: yes
-u zuul wait_timeout: 300
-p "{{ _registry_password.stdout_lines[0] | b64decode }}" wait_condition:
type: Complete
status: "True"

View File

@ -156,3 +156,6 @@
- name: Test the preview - name: Test the preview
include_tasks: ./tasks/test_preview.yaml include_tasks: ./tasks/test_preview.yaml
- name: Test the registry
include_tasks: ./tasks/test_registry.yaml

View File

@ -0,0 +1,107 @@
{%- if manage_registry_cert %}
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: zuul-registry-tls
labels:
app.kubernetes.io/name: zuul
app.kubernetes.io/instance: {{ instance_name }}
app.kubernetes.io/part-of: zuul
app.kubernetes.io/component: zuul-registry-tls
spec:
keyEncoding: pkcs8
secretName: zuul-registry-tls
commonName: client
usages:
- digital signature
- key encipherment
- server auth
- client auth
issuerRef:
name: ca-issuer
kind: Issuer
{%- endif %}
---
apiVersion: v1
kind: Service
metadata:
name: zuul-registry
labels:
app.kubernetes.io/name: zuul
app.kubernetes.io/instance: {{ instance_name }}
app.kubernetes.io/part-of: zuul
app.kubernetes.io/component: zuul-registry
spec:
type: NodePort
ports:
- name: zuul-registry
port: 443
protocol: TCP
targetPort: registry
selector:
app.kubernetes.io/name: zuul
app.kubernetes.io/instance: {{ instance_name }}
app.kubernetes.io/part-of: zuul
app.kubernetes.io/component: zuul-registry
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zuul-registry
labels:
app.kubernetes.io/name: zuul
app.kubernetes.io/instance: {{ instance_name }}
app.kubernetes.io/part-of: zuul
app.kubernetes.io/component: zuul-registry
spec:
replicas: {{ spec.registry.count }}
serviceName: zuul-registry
selector:
matchLabels:
app.kubernetes.io/name: zuul
app.kubernetes.io/instance: {{ instance_name }}
app.kubernetes.io/part-of: zuul
app.kubernetes.io/component: zuul-registry
template:
metadata:
labels:
app.kubernetes.io/name: zuul
app.kubernetes.io/instance: {{ instance_name }}
app.kubernetes.io/part-of: zuul
app.kubernetes.io/component: zuul-registry
spec:
containers:
- name: registry
image: {{ spec.imagePrefix }}/zuul-registry:{{ spec.zuulImageVersion }}
env:
- name: DEBUG
value: '1'
ports:
- name: registry
containerPort: 9000
volumeMounts:
- name: zuul-registry-config
mountPath: /conf
readOnly: true
- name: zuul-registry-tls
mountPath: /tls
readOnly: true
- name: zuul-registry
mountPath: /storage
volumes:
- name: zuul-registry-config
secret:
secretName: zuul-registry-generated-config
- name: zuul-registry-tls
secret:
secretName: {{ spec.registry.tls.secretName }}
volumeClaimTemplates:
- metadata:
name: zuul-registry
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ spec.registry.volumeSize }} #80Gi

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import kopf
import copy import copy
import base64 import base64
import hashlib import hashlib
@ -72,10 +73,17 @@ class Zuul:
self.spec.setdefault('web', {}).setdefault('count', 1) self.spec.setdefault('web', {}).setdefault('count', 1)
self.spec.setdefault('fingergw', {}).setdefault('count', 1) self.spec.setdefault('fingergw', {}).setdefault('count', 1)
self.spec.setdefault('preview', {}).setdefault('count', 0) self.spec.setdefault('preview', {}).setdefault('count', 0)
registry = self.spec.setdefault('registry', {})
registry.setdefault('count', 0)
registry.setdefault('volumeSize', '80Gi')
registry_tls = registry.setdefault('tls', {})
self.manage_registry_cert = ('secretName' not in registry_tls)
registry_tls.setdefault('secretName', 'zuul-registry-tls')
self.spec.setdefault('imagePrefix', 'docker.io/zuul') self.spec.setdefault('imagePrefix', 'docker.io/zuul')
self.spec.setdefault('zuulImageVersion', 'latest') self.spec.setdefault('zuulImageVersion', 'latest')
self.spec.setdefault('zuulPreviewImageVersion', 'latest') self.spec.setdefault('zuulPreviewImageVersion', 'latest')
self.spec.setdefault('zuulRegistryImageVersion', 'latest')
self.spec.setdefault('nodepoolImageVersion', 'latest') self.spec.setdefault('nodepoolImageVersion', 'latest')
self.cert_manager = certmanager.CertManager( self.cert_manager = certmanager.CertManager(
@ -314,7 +322,55 @@ class Zuul:
except pykube.exceptions.ObjectDoesNotExist: except pykube.exceptions.ObjectDoesNotExist:
pass pass
def write_registry_conf(self):
config_secret = self.spec['registry'].get('config', {}).get('secretName')
if not config_secret:
raise kopf.PermanentError("No registry config secret found")
try:
obj = objects.Secret.objects(self.api).\
filter(namespace=self.namespace).\
get(name=config_secret)
except pykube.exceptions.ObjectDoesNotExist:
raise kopf.TemporaryError("Registry config secret not found")
# Shard the config so we can create a deployment + secret for
# each provider.
registry_yaml = yaml.safe_load(base64.b64decode(
obj.obj['data']['registry.yaml']))
reg = registry_yaml['registry']
if 'public-url' not in reg:
reg['public-url'] = 'https://zuul-registry'
reg['address'] = '0.0.0.0'
reg['port'] = 9000
reg['tls-cert'] = '/tls/tls.crt'
reg['tls-key'] = '/tls/tls.key'
reg['secret'] = utils.generate_password(56)
reg['storage'] = {
'driver': 'filesystem',
'root': '/storage',
}
text = yaml.dump(registry_yaml)
utils.update_secret(self.api, self.namespace,
'zuul-registry-generated-config',
string_data={'registry.yaml': text})
def create_registry(self):
self.write_registry_conf()
kw = {
'instance_name': self.name,
'spec': self.spec,
'manage_registry_cert': self.manage_registry_cert,
}
utils.apply_file(self.api, 'zuul-registry.yaml',
namespace=self.namespace, **kw)
def create_zuul(self): def create_zuul(self):
if self.spec['registry']['count']:
self.create_registry()
kw = { kw = {
'zuul_conf_sha': self.zuul_conf_sha, 'zuul_conf_sha': self.zuul_conf_sha,
'zuul_tenant_secret': self.tenant_secret, 'zuul_tenant_secret': self.tenant_secret,