Skip to content

Instantly share code, notes, and snippets.

@ssro
Last active February 10, 2022 03:54
Show Gist options
  • Save ssro/feecfdf89d35a1426bba852a35bac389 to your computer and use it in GitHub Desktop.
Save ssro/feecfdf89d35a1426bba852a35bac389 to your computer and use it in GitHub Desktop.

Docker registry mirror (a.k.a. Registry as a pull through cache)

Recipes from here, cooked and baked by ssro

AWS environment, K3S kubernetes environment

Make sure that the instance used for this setup has Route53 permissions (proper instance role)

Can use persistent volumes or attached disk. In this case, there's a disk attached to the instance as /data and XFS formatted

After the setup is complete, use https://registry-1.domain.tld (please use your own domain) as your mirror (reconfigure docker daemons running in your infra to use this as mirror)

Install k3s

curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v1.23.3+k3s1" INSTALL_K3S_EXEC="server --write-kubeconfig-mode 644 --disable servicelb --disable traefik --kubelet-arg eviction-hard=memory.available<300Mi --kubelet-arg=image-gc-high-threshold=85 --kubelet-arg=image-gc-low-threshold=80 --kube-apiserver-arg=enable-aggregator-routing=true" sh -

Apply JetStack Cert Manager

kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.0/cert-manager.yaml

Apply metallb

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/metallb.yaml
export IP=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)

or

export IP=$(hostname -I | cut -d " " -f1)

Create configmap for metallb

tee << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - ${IP}/32
EOF

Apply the rest of the manifest below but replace the following to match your setup

SOMERANDOMSTRING
registry-1.domain.tld
YOUR_EMAIL_ADDRESS
YOUR_REGION
YOUR_ZONE_ID_AS_IT_APPEARS_IN_ROUTE53

Apply ingress controller & patches

mkdir ~/manifests/ingress-nginx && cd ~/manifests/ingress-nginx
curl -o ingress-nginx-controller.yaml https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.1/deploy/static/provider/cloud/deploy.yaml

cat << EOF > cm-ingress-controller.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  allow-snippet-annotations: "true"
  use-forwarded-headers: "true"
  enable-brotli: "true"
  hsts: "true"
  hsts-include-subdomains: "true"
  hsts-max-age: "63072000"
  server-tokens: "false"
  ssl-ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
  ssl-protocols: TLSv1.2 TLSv1.3
  ssl-session-cache: "true"
  ssl-session-cache-size: 20m
  ssl-session-tickets: "false"
  use-gzip: "true"
  gzip-level: "3"
EOF

cat <<EOF > kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ingress-nginx-controller.yaml
patches:
- cm-ingress-controller.yaml
namespace: ingress-nginx
EOF

kubectl apply -k .

Apply the registry manifest & dependencies:

apiVersion: v1
kind: Namespace
metadata:
  name: registry
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-configuration
  namespace: registry
data:
  redis-cfg: |-
    maxmemory 1g
    activedefrag yes
    active-defrag-threshold-lower 10
    active-defrag-threshold-upper 100
    tcp-backlog 65535
    maxmemory-policy allkeys-lru
    hash-max-ziplist-entries 512
    hash-max-ziplist-value 64
    zset-max-ziplist-entries 128
    zset-max-ziplist-value 64
    set-max-intset-entries 512
    client-output-buffer-limit normal 0 0 0
    client-output-buffer-limit replica 256mb 64mb 60
    client-output-buffer-limit pubsub 256mb 64mb 60
    stop-writes-on-bgsave-error no

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: redis
  name: redis
  namespace: registry
spec:
  ports:
  - port: 6379
  selector:
    app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: redis
  name: redis
  namespace: registry
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: redis
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: "redis"
        preset: "true"
    spec:
      serviceAccountName: redis
      containers:
      - image: redis:6.2.5-alpine
        securityContext:
          runAsNonRoot: true
          runAsUser: 999
          allowPrivilegeEscalation: false
        imagePullPolicy: IfNotPresent
        livenessProbe:
          exec:
            command:
            - redis-cli
            - ping
          failureThreshold: 5
          initialDelaySeconds: 30
          timeoutSeconds: 5
        name: redis
        command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
        ports:
        - containerPort: 6379
          name: redis
        readinessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 1
        resources:
          limits:
            cpu: 1
            memory: 1.5Gi
          requests:
            cpu: 200m
            memory: 128Mi
        volumeMounts:
        - mountPath: /data
          name: data
        - mountPath: /usr/local/etc/redis
          name: config
      initContainers:
      - command:
        - sysctl
        - -w
        - net.core.somaxconn=65535
        image: busybox:1.34.0
        imagePullPolicy: IfNotPresent
        name: somaxconn
        securityContext:
          privileged: true
      - command:
        - sysctl
        - -w
        - vm.overcommit_memory=1
        image: busybox:1.34.0
        imagePullPolicy: IfNotPresent
        name: mem-ovrcommit
        securityContext:
          privileged: true
      - command:
        - sysctl
        - -w
        - net.ipv4.tcp_max_syn_backlog=65535
        image: busybox:1.34.0
        imagePullPolicy: IfNotPresent
        name: syn-backlog
        securityContext:
          privileged: true
      - name: disable-thp
        image: busybox
        command: ["sh", "-c"]
        args:
        - |-
          set -e
          set -o pipefail
          echo never > /rootfs/sys/kernel/mm/transparent_hugepage/enabled
          echo never > /rootfs/sys/kernel/mm/transparent_hugepage/defrag
          grep -q -F [never] /sys/kernel/mm/transparent_hugepage/enabled
          grep -q -F [never] /sys/kernel/mm/transparent_hugepage/defrag
        securityContext:
          privileged: true
        volumeMounts:
        - name: sys
          mountPath: /rootfs/sys
      volumes:
      - emptyDir: {}
        name: data
      - name: config
        configMap:
          name: redis-configuration
          items:
          - key: redis-cfg
            path: redis.conf
      - name: sys
        hostPath:
          path: /sys

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: registry
  namespace: registry
data:
  registry-config.yml: |
    version: 0.1
    log:
      fields:
        service: registry
    storage:
      filesystem:
        rootdirectory: /data/registry
      cache:
        blobdescriptor: redis
      maintenance:
        uploadpurging:
          enabled: true
          age: 120h
          interval: 12h
          dryrun: false
      delete:
        enabled: true
    http:
      addr: :5000
      headers:
        X-Content-Type-Options: [nosniff]
        # Access-Control-Allow-Origin: ['*']
        # Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS']
        # Access-Control-Max-Age: [1728000]
        # Access-Control-Expose-Headers: ['Docker-Content-Digest']
      secret: SOMERANDOMSTRING
      draintimeout: 60s
    redis:
      addr: redis:6379
      db: 0
      dialtimeout: 10ms
      readtimeout: 10ms
      writetimeout: 10ms
      pool:
        maxidle: 16
        maxactive: 64
        idletimeout: 300s
    health:
      storagedriver:
        enabled: true
        interval: 10s
        threshold: 3
    proxy:
      remoteurl: https://registry-1.docker.io
      # username: [username]
      # password: [password]
---
apiVersion: v1
kind: Service
metadata:
  name: registry
  namespace: registry
  labels:
    app: registry
spec:
  selector:
    app: registry
  ports:
    - port: 5000
      name: http-5000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry
  namespace: registry
  labels:
    app: registry
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  selector:
    matchLabels:
      app: registry
  revisionHistoryLimit: 3
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: registry
    spec:
      volumes:
      - name: registry
        hostPath:
          path: /data/registry
      - name: config
        configMap:
          name: registry
          items:
          - key: registry-config.yml
            path: config.yml
      containers:
        - name: registry
          image: registry:2.7.1
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 5000
              name: http-5000
              protocol: TCP
          resources:
            limits:
              cpu: 1
              memory: 2Gi
            requests:
              cpu: 200m
              memory: 128Mi
          volumeMounts:
            - name: config
              mountPath: /etc/docker/registry
              readOnly: true
            - name: registry
              mountPath: /data/registry
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: registry
  namespace: registry
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/proxy-body-size: "500m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-buffer-size: "256k"
    nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout http_502"
    nginx.ingress.kubernetes.io/http2-max-header-size: "256k"
    nginx.ingress.kubernetes.io/large-client-header-buffers: "256 256k"
    nginx.ingress.kubernetes.io/client-header-buffer-size: "256k"
    nginx.ingress.kubernetes.io/client-body-buffer-size: "5m"
    nginx.ingress.kubernetes.io/use-gzip: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Content-Type-Options: nosniff";
      more_set_headers "X-Frame-Options: SAMEORIGIN";
      more_set_headers "X-Xss-Protection: 1; mode=block";
      more_set_headers "Content-Security-Policy: upgrade-insecure-requests";
      more_set_headers "Referrer-Policy: no-referrer-when-downgrade";
spec:
  ingressClassName: nginx
  tls:
  - secretName: tls-secret
    hosts:
    - registry-1.domain.tld
  rules:
  - host: registry-1.domain.tld
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: registry
            port:
              number: 5000
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: YOUR_EMAIL_ADDRESS
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - selector: {}
      dns01:
        route53:
          region: YOUR_REGION
          hostedZoneID: YOUR_ZONE_ID_AS_IT_APPEARS_IN_ROUTE53
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: tls-secret
  namespace: registry
spec:
  secretName: tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: '*.domain.tld'
  dnsNames:
  - '*.domain.tld'
---
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment