Hosting a Private Docker Registry - DigitalOcean Kubernetes - Part 2
In my previous article, I set up a Kubernetes cluster with hosting, ingress, and TLS. This time my goal was to deploy a private Docker registry on the cluster. Then upload an image to the registry and deploy it on the cluster.
Again, there's documentation on how to do that via DigitalOcean. This was my starting point, and I made a few adjustments along the way.
DNS
My first task was setting up another A record to point to the cluster. I went with registry.mattkruskamp.com to stay with the tutorial as close as possible.
Creating spaces storage
The registry services run on the cluster, but the services need a place to store the images. That's where DigitalOcean spaces come in. Again, creating things in DigitalOcean is easy. Click the Create button in the top right.
I entered the basic information for my space.
Once created, I uploaded a file to it. For some reason having a file is required for initialization.
Access keys must be generated to authenticate the cluster with the space. In the spaces section click the Manage Keys button on the right. In the Tokens/Keys section there's a Spaces access keys section.
At this point, it's time to do some configuration. The first thing I did was remove the production instances I created from the first article.
kubectl delete -f hello-kubernetes-ingress.yaml
kubectl delete -f hello-kubernetes-first.yaml
Then create a workspace to add files.
mkdir k8s-registry
cd k8s-registry/
Set up a file to override the defaults of the Helm installation.
chart-values.yaml
ingress:
enabled: true
hosts:
- <registry.yoursite.com>
annotations:
kubernetes.io/ingress.class: nginx
certmanager.k8s.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: '30720m'
tls:
- secretName: letsencrypt-prod
hosts:
- <registry.yoursite.com>
storage: s3
secrets:
htpasswd: ''
s3:
accessKey: '<your-space-access-key>'
secretKey: '<your-space-secret-key>'
s3:
region: <your-space-region>
regionEndpoint: <your-space-region.digitaloceanspaces.com>
secure: true
bucket: <your-space-name>
I was a little confused about what to configure. The the hosts
and tls
values get set to the A record setup. The secretName
value must be unique to this ingress. The accessKey
and secretKey
are pulled from creating the keys in the previous step. The region
refers to the region prefix for the data center used. In my case it was sfo2
. The same region's used for regionEndpoint
. Ex: sfo2.digitaloceanspaces.com
. Finally, bucket
is the name of the space. Now Helm comes back to install the registry.
helm repo update
helm install stable/docker-registry -f chart_values.yaml --name docker-registry
Resources get created for the docker-registry. At this point, it's time to test it out.
Testing the registry
The best way to test is by pulling an existing repo, re-tagging it, and pushing it to the new registry.
sudo docker pull mysql
sudo docker tag mysql <registry.yoursite.com>/mysql
sudo docker push <registry.yoursite.com>/mysql
Once this was successful, clean up the existing resources and pull it from the new remote registry.
sudo docker rmi <registry.yoursite.com>/mysql && sudo docker rmi mysql
sudo docker pull <registry.yoursite.com>/mysql
Now verify the image is there.
sudo docker images
Securing the registry
A private repository isn't valuable if there's no authentication. Securing it is done by adding usernames and passwords to the configuration via htpasswd. Htpasswd can get accessed via a docker image.
docker run --rm -v ${PWD}:/app -it httpd htpasswd -b -c /app/htpasswd_file <username> <password>
touch htpasswd_file
Then username and password combinations are added to the file.
htpasswd -B htpasswd_file <username>
Once all the usernames and passwords get added, I opened the file and copied the contents. Then the configuration for the registry is opened and htpasswd: ""
section gets replaced with htpasswd: |-
followed by the usernames and password hashes.
chart-values.yaml
---
secrets:
htpasswd: |-
<username>:<password-hash>
Next, the registry is updated to include the authentication configuration.
helm upgrade docker-registry stable/docker-registry -f chart-values.yaml
This is easily tested by trying to pull the image down.
sudo docker pull <registry.yoursite.com>/mysql
Retry after authenticating docker and trying again.
sudo docker login <registry.yoursite.com>
sudo docker pull <registry.yoursite.com>/mysql
The next step was to authenticate the Kubernetes cluster with the registry via sudo kubectl create secret generic regcred --from-file=.dockerconfigjson=/home/user/.docker/config.json --type=kubernetes.io/dockerconfigjson
. This didn't work for me due to Mac not storing the auth credentials in the config.json. I searched the comments and found a command that works.
kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword>
Running a deployment
In the previous article, I used the hello-kubernetes
docker image to test deployments. I did the same this time around by tagging a private version and deploying it. Then I created another configuration file and publishing it using the private registry.
sudo docker pull paulbouwer/hello-kubernetes:1.5
sudo docker tag paulbouwer/hello-kubernetes:1.5 <registry.yoursite.com>/paulbouwer/hello-kubernetes:1.5
sudo docker push <registry.yoursite.com>/paulbouwer/hello-kubernetes:1.5
hello-world.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: hello-kubernetes-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- <your.domain.com>
secretName: hello-kubernetes-tls
rules:
- host: <your.domain.com>
http:
paths:
- path: /
backend:
serviceName: hello-kubernetes
servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-kubernetes
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
selector:
app: hello-kubernetes
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-kubernetes
spec:
replicas: 3
selector:
matchLabels:
app: hello-kubernetes
template:
metadata:
labels:
app: hello-kubernetes
spec:
containers:
- name: hello-kubernetes
image: <registry.yoursite.com>/paulbouwer/hello-kubernetes:1.5
ports:
- containerPort: 8080
imagePullSecrets:
- name: regcred
Update the cluster with the new configuration.
kubectl apply -f hello-world.yaml
Finally, go see the new site.
Impressions
I'm happy Helm has configurations for private registries. The documentation is good, but I did run into a few snags that took some time to unwind. When I first set everything up I kept getting a "certificate is valid for ingress.local" error. This was sorted out by understanding every ingress must have a unique secretName
. This documentation exists, but I missed it. The second issue was setting up regcred
. This problem is a difference between Mac and Linux, but luckily the commenters on the article figured it out.
I don't like the htaccess solution for password management. I'm assuming there's a lot of ways to handle that so I'll be looking into better ways. Despite those small issues, I'm very happy with the cluster. It's been rock solid while I've been working with it, and the DigitalOcean monitoring tools are fantastic.
Next steps
In the next article I'll be converting the site to a Docker container and deploying it.