What would be the best setup to run sonatype\nexus3
in Kubernetes that allows using the Docker repositories?
Currently I have a basic setup:
sonatype\nexus3
How do I get around the limitation of ingress that doesn't allow more than one port?
Nexus needs to be served over SSL, otherwise docker won't connect to it. This can be achieved with a k8s ingress + kube-lego for a Let's Encrypt certificate. Any other real certificate will work as well. However, in order to serve both the nexus UI and the docker registry through one ingress (thus, one port) one needs a reverse proxy behind the ingress to detect the docker user agent and forward the request to the registry.
--(IF user agent docker) --> [nexus service]nexus:5000 --> docker registry
|
[nexus ingress]nexus.example.com:80/ --> [proxy service]internal-proxy:80 -->|
|
--(ELSE ) --> [nexus service]nexus:80 --> nexus UI
nexus-deployment.yaml This makes use of an azureFile volume, but you can use any volume. Also, the secret is not shown, for obvious reasons.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nexus
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nexus
spec:
containers:
- name: nexus
image: sonatype/nexus3:3.3.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8081
- containerPort: 5000
volumeMounts:
- name: nexus-data
mountPath: /nexus-data
resources:
requests:
cpu: 440m
memory: 3.3Gi
limits:
cpu: 440m
memory: 3.3Gi
volumes:
- name: nexus-data
azureFile:
secretName: azure-file-storage-secret
shareName: nexus-data
It is always a good idea to add health and readiness probes, so that kubernetes can detect when the app goes down. Hitting the index.html
page doesn't always work very well, so I'm using the REST API instead. This requires adding the Authorization header for a user with the nx-script-*-browse
permission. Obviously you'll have to first bring the system up without probes to set up the user, then update your deployment later.
readinessProbe:
httpGet:
path: /service/siesta/rest/v1/script
port: 8081
httpHeaders:
- name: Authorization
# The authorization token is simply the base64 encoding of the `healthprobe` user's credentials:
# $ echo -n user:password | base64
value: Basic dXNlcjpwYXNzd29yZA==
initialDelaySeconds: 900
timeoutSeconds: 60
livenessProbe:
httpGet:
path: /service/siesta/rest/v1/script
port: 8081
httpHeaders:
- name: Authorization
value: Basic dXNlcjpwYXNzd29yZA==
initialDelaySeconds: 900
timeoutSeconds: 60
Because nexus can sometimes take a long time to start, I use a very generous initial delay and timeout.
nexus-service.yaml Expose port 80 for the UI, and port 5000 for the registry. This must correspond to the port configured for the registry through the UI.
apiVersion: v1
kind: Service
metadata:
labels:
app: nexus
name: nexus
namespace: default
selfLink: /api/v1/namespaces/default/services/nexus
spec:
ports:
- name: http
port: 80
targetPort: 8081
- name: docker
port: 5000
targetPort: 5000
selector:
app: nexus
type: ClusterIP
proxy-configmap.yaml The nginx.conf is added as ConfigMap data volume. This includes a rule for detecting the docker user agent. This relies on the kubernetes DNS to access the nexus
service as upstream.
apiVersion: v1
data:
nginx.conf: |
worker_processes auto;
events {
worker_connections 1024;
}
http {
error_log /var/log/nginx/error.log warn;
access_log /dev/null;
proxy_intercept_errors off;
proxy_send_timeout 120;
proxy_read_timeout 300;
upstream nexus {
server nexus:80;
}
upstream registry {
server nexus:5000;
}
server {
listen 80;
server_name nexus.example.com;
keepalive_timeout 5 5;
proxy_buffering off;
# allow large uploads
client_max_body_size 1G;
location / {
# redirect to docker registry
if ($http_user_agent ~ docker ) {
proxy_pass http://registry;
}
proxy_pass http://nexus;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto "https";
}
}
}
kind: ConfigMap
metadata:
creationTimestamp: null
name: internal-proxy-conf
namespace: default
selfLink: /api/v1/namespaces/default/configmaps/internal-proxy-conf
proxy-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: internal-proxy
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
proxy: internal
spec:
containers:
- name: nginx
image: nginx:1.11-alpine
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
volumeMounts:
- name: internal-proxy-conf
mountPath: /etc/nginx/
env:
# This is a workaround to easily force a restart by incrementing the value (numbers must be quoted)
# NGINX needs to be restarted for configuration changes, especially DNS changes, to be detected
- name: RESTART_
value: "0"
volumes:
- name: internal-proxy-conf
configMap:
name: internal-proxy-conf
items:
- key: nginx.conf
path: nginx.conf
proxy-service.yaml The proxy is deliberately of type ClusterIP
because the ingress will forward traffic to it. Port 443 is not used in this example.
kind: Service
apiVersion: v1
metadata:
name: internal-proxy
namespace: default
spec:
selector:
proxy: internal
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
type: ClusterIP
nexus-ingress.yaml This step assumes you have an nginx ingress controller. If you have a certificate you don't need an ingress and can instead expose the proxy service, but you won't have the automation benefits of kube-lego.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nexus
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
kubernetes.io/tls-acme: "true"
spec:
tls:
- hosts:
- nexus.example.com
secretName: nexus-tls
rules:
- host: nexus.example.com
http:
paths:
- path: /
backend:
serviceName: internal-proxy
servicePort: 80
I think this could be done just using the nginx ingress. by using a path or sub domain for you ingress. For example:
Service
apiVersion: v1
kind: Service
metadata:
labels:
app: nexus
name: nexus
namespace: default
selfLink: /api/v1/namespaces/default/services/nexus
spec:
ports:
- name: http
port: 80
targetPort: 8081
- name: docker
port: 5000
targetPort: 5000
selector:
app: nexus
type: ClusterIP
Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nexus
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
kubernetes.io/tls-acme: "true"
spec:
tls:
- hosts:
- nexus.example.com
- docker.example.com
secretName: nexus-tls
rules:
- host: nexus.example.com
http:
paths:
- path: /
backend:
serviceName: nexus
servicePort: 80
- host: docker.example.com
http:
paths:
- path: /
backend:
serviceName: nexus
servicePort: 5000
Here https://nexus.example.com
will get you to you Nexus UI and all registry functions that work over the normal HTTP port. https://docker.example.com:5000
will expose your docker repo. While this requires you using two different hostnames, its a little more explicit and doesn't rely on the client properly setting the user agent. This also happens to be the strategy that the Nexus Helm chart uses, as seen here:
https://github.com/kubernetes/charts/tree/master/stable/sonatype-nexus
This assumes you have nginx ingress with proper TLS configuration running and your cluster can handle Persistent Volume Claims:
Install Nexus
Install Nexus in your cluster using helm:
helm install stable/sonatype-nexus --name registry --namespace foo
Note: You can revoke the installation with this command:
helm del --purge registry
Adjust Nexus deployment
After installing Nexus with helm you'll find a deployment for Nexus. Add containerPort: 5000
to it, right below the containterPort that is already there.
Adjust Nexus Service
You also need to add port 5000
to the Nexus service. Put it just below the default port:
- port: 5000
targetPort: 5000
protocol: TCP
name: docker
Example Ingress config:
This config points https://registry.example.com
to the Nexus UI at port 8081
and it points https://docker.exmaple.com
to the docker service at port 5000
.
Note: In my case port 8081
was the default port that was provided in the Nexus deployment, which you edited in the step above. Adjust it, if your installation uses another port.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example-com-ingress
namespace: foo
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
# Provide the docker backend that is used for docker login.
- host: docker.example.com
http:
paths:
- path: /
backend:
serviceName: registry-sonatype-nexus
servicePort: 5000
# Provide the nexus backend that is used for the UI etc.
- host: registry.exmaple.com
http:
paths:
- path: /
backend:
serviceName: registry-sonatype-nexus
servicePort: 8081
tls:
- secretName: example-com-tls
hosts:
- registry.example.com
- docker.example.com
Configure Nexus
You should now be able to open the Nexus UI at https://registry.example.com
. Login with default credentials. user: admin
pw: admin123
.
Create a docker host repo and set HTTP
under Repository Connector
to 5000
and disable Force Basic Authentication
.
Login, tag and push an image
You should now be able to log your docker client in to the registry using Nexus login credentials:
docker login docker.example.com
Use this pattern to tag and push an image:
docker tag <image>:<tag> <nexus-hostname>/<namespace>/<image>:<tag>
docker push <nexus-hostname>/<namespace>/<image>:<tag>
E.g:
docker tag myapp:1.0.0 docker.example.com/foo/myapp:1.0.0
docker push docker.example.com/foo/myapp:1.0.0