Serve every tool on deployshed.com instead of nip.io

Harbor, Gitea, Argo CD, Jenkins, Vault, Grafana and vmui now answer on
their deployshed.com names alone. Each was already serving both while the
move was proved out; this removes the nip.io half.

The dual-hostname workarounds go with it. Jenkins' secondaryingress existed
only because its chart's primary ingress takes one hostName and a
certificate could not span both names — the real domain moves onto the
primary with jenkins-tls, which it already holds. Argo CD gets extraTls
rather than ingress.tls, because the boolean hardcodes secretName
argocd-server-tls and would request a second certificate for a name that
already has a valid one in argocd-deployshed-tls.

Harbor also changes in two ways beyond the hostname:

  - externalURL moves to https://harbor.infra.deployshed.com. Harbor hands
    this to docker clients in its own API responses and builds the push
    commands shown in its UI from it, so a stale value is what makes a
    correctly-configured registry still advertise the old address.

  - updateStrategy is now Recreate. Its jobservice and registry volumes are
    standard-rwo (ReadWriteOnce), and a RollingUpdate starts the new pod
    before the old one releases the disk, so the replacement hangs forever
    on Multi-Attach. The cluster was sitting in exactly that state, old pods
    serving while new ones stayed in ContainerCreating. The chart's own
    comment on this value recommends Recreate when RWM is unavailable. The
    cost is a brief outage during upgrades, which beats a rollout that
    cannot complete.

The private registry CA is not removed yet. Apps deployed before this move
recorded nip.io image references that only change when each is rebuilt, so
the old hostname stays served by a standalone Ingress until then.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEsTefWWifp4ikvhHF5s6N
This commit is contained in:
Mukul Sharma
2026-09-17 09:31:26 +05:30
co-authored by Claude Opus 5
parent df3e0a41af
commit 61bc4af1a0
8 changed files with 106 additions and 75 deletions
@@ -81,21 +81,23 @@ argo-cd:
ingress: ingress:
enabled: true enabled: true
ingressClassName: contour ingressClassName: contour
hostname: "argocd.35.238.248.203.nip.io" hostname: "argocd.infra.deployshed.com"
# The chart supports additional hostnames natively, so the new domain # TLS via extraTls rather than the `tls: true` boolean, deliberately.
# is served here rather than from a second Ingress object.
extraHosts:
- name: "argocd.infra.deployshed.com"
path: /
# No TLS yet, deliberately. This chart's ingress.tls is a boolean, not
# a host list: turning it on requests ONE certificate covering
# `hostname` plus every extraHost, and Let's Encrypt cannot issue for
# nip.io — so the request would fail and neither name would be served
# over TLS. There is no extraTls to scope it more narrowly.
# #
# This one gets its certificate when nip.io is retired and `hostname` # The boolean hardcodes `secretName: argocd-server-tls` (see the
# itself becomes the deployshed.com name. Until then ArgoCD is HTTP # chart's argocd-server/ingress.yaml). This deployment already holds a
# only, as it already was. # valid, issued certificate for this exact hostname in
# argocd-deployshed-tls, created by the standalone Ingress that served
# the real domain while nip.io was still on `hostname`. Flipping the
# boolean would ignore that and request a second certificate for the
# same name — a needless issuance and a gap while it is obtained.
#
# extraTls takes an explicit secretName, so the existing certificate is
# adopted as-is and the standalone Ingress can simply be deleted.
extraTls:
- hosts:
- argocd.infra.deployshed.com
secretName: argocd-deployshed-tls
resources: resources:
requests: requests:
cpu: 50m cpu: 50m
@@ -129,7 +131,10 @@ argo-cd:
params: params:
server.insecure: true server.insecure: true
cm: cm:
url: "http://argocd.35.238.248.203.nip.io" # https now that the only hostname served carries a real certificate.
# This is what ArgoCD builds its own links from, so leaving it http
# would hand out plain-HTTP URLs for a TLS-only deployment.
url: "https://argocd.infra.deployshed.com"
timeout.reconciliation: 3m timeout.reconciliation: 3m
timeout.reconciliation.jitter: 60s timeout.reconciliation.jitter: 60s
# No Ingress health override, unlike the homelab: there Contour sat # No Ingress health override, unlike the homelab: there Contour sat
@@ -35,10 +35,16 @@ contour:
type: LoadBalancer type: LoadBalancer
# The reserved address from Terraform (module.network's # The reserved address from Terraform (module.network's
# google_compute_address). Every hostname in this deployment is # google_compute_address). Every hostname in this deployment — the
# <name>.35.238.248.203.nip.io, so this pin is what makes DNS work at # deployshed.com records, including the two wildcards — resolves here,
# all an unpinned Service takes a fresh ephemeral IP and every # so this pin is what makes DNS work at all: an unpinned Service takes
# hostname points at nothing. # a fresh ephemeral IP and every hostname points at nothing.
#
# More load-bearing now, not less, than when hostnames were
# <name>.35.238.248.203.nip.io. Those encoded the address, so a changed
# IP produced names that were merely wrong. Real DNS records point here
# until somebody edits them in Cloudflare, so a changed IP is an
# outage across every hostname at once.
# #
# spec.loadBalancerIP is deprecated upstream (Kubernetes 1.24), and # spec.loadBalancerIP is deprecated upstream (Kubernetes 1.24), and
# GKE's replacement is the annotation # GKE's replacement is the annotation
@@ -43,7 +43,7 @@ gitea:
actions: actions:
ENABLED: true ENABLED: true
server: server:
ROOT_URL: http://gitea.35.238.248.203.nip.io/ ROOT_URL: https://gitea.infra.deployshed.com/
service: service:
# The homelab sat on a LAN; this Gitea is on a public IP. Open # The homelab sat on a LAN; this Gitea is on a public IP. Open
# registration would let anyone on the internet create an account. # registration would let anyone on the internet create an account.
@@ -69,7 +69,7 @@ gitea:
# The proper fix is a configurable webhook base URL in toolshed # The proper fix is a configurable webhook base URL in toolshed
# pointing at toolshed-api.toolshed.svc.cluster.local:8080, after # pointing at toolshed-api.toolshed.svc.cluster.local:8080, after
# which this entry should be removed. # which this entry should be removed.
ALLOWED_HOST_LIST: private,toolshed.35.238.248.203.nip.io ALLOWED_HOST_LIST: private,console.deployshed.com
admin: admin:
username: gitadmin username: gitadmin
# Created by hand with kubectl at bootstrap, because Vault and ESO # Created by hand with kubectl at bootstrap, because Vault and ESO
@@ -82,22 +82,20 @@ gitea:
# Contour does not exist yet at bootstrap — the Ingress just sits unused # Contour does not exist yet at bootstrap — the Ingress just sits unused
# until ArgoCD installs it. # until ArgoCD installs it.
# #
# Two hosts while the deployment moves onto its own domain. The nip.io one # One host. The nip.io name was served alongside this one while the
# stays until everything that references it has been repointed — ROOT_URL # deployment moved onto its own domain, and came out once everything
# below, and any git remote anyone has configured. # referencing it had been repointed: ROOT_URL above, the webhook allow-list
# above that, and any git remote anyone had configured.
ingress: ingress:
enabled: true enabled: true
className: contour className: contour
annotations: annotations:
# Issues the certificate named in tls below. Only the real domain is # Issues the certificate named in tls below. This could only ever cover
# listed there: Let's Encrypt cannot issue for nip.io, and asking for # the real domain: Let's Encrypt cannot issue for nip.io, so while both
# one certificate spanning both names returns nothing for either. # names were served, asking for one certificate spanning them returned
# nothing for either.
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
hosts: hosts:
- host: gitea.35.238.248.203.nip.io
paths:
- path: /
pathType: Prefix
- host: gitea.infra.deployshed.com - host: gitea.infra.deployshed.com
paths: paths:
- path: / - path: /
@@ -268,13 +268,14 @@ grafana:
enabled: true enabled: true
ingressClassName: contour ingressClassName: contour
annotations: annotations:
# Only the real domain appears in tls below — Let's Encrypt cannot # The real domain is now the only host, so this covers everything
# issue for nip.io, and one certificate spanning both would fail # served. While nip.io was alongside it, only the real domain could
# outright rather than covering the half it can serve. # appear in tls below — Let's Encrypt cannot issue for nip.io, and one
# certificate spanning both would have failed outright rather than
# covering the half it could serve.
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
path: / path: /
hosts: hosts:
- grafana.35.238.248.203.nip.io
- grafana.infra.deployshed.com - grafana.infra.deployshed.com
tls: tls:
- secretName: grafana-tls - secretName: grafana-tls
@@ -6,41 +6,69 @@ harbor:
# The homelab runs Harbor on plain HTTP and works around containerd's # The homelab runs Harbor on plain HTTP and works around containerd's
# refusal to pull from an insecure registry by hand-editing # refusal to pull from an insecure registry by hand-editing
# /etc/containerd/certs.d/<host>/hosts.toml on the node. GKE nodes are # /etc/containerd/certs.d/<host>/hosts.toml on the node. GKE nodes are
# managed and replaced, so that edit cannot survive. Instead the node pool # managed and replaced, so that edit cannot survive.
# is told, at creation, to trust a private CA for exactly this hostname,
# fetching it from Secret Manager (see toolshed-gke-infra's gke-cluster
# module). cert-manager signs Harbor's certificate from that same CA.
# #
# Let's Encrypt is not an option: nip.io is not on the public suffix list, # That used to mean a private CA: the registry was a nip.io name, Let's
# so every *.nip.io certificate on the internet shares one rate limit. # Encrypt cannot issue for one (not on the public suffix list, and every
# A private CA is fine for image pulls, which is what matters, but # *.nip.io certificate shares a single rate limit), so Terraform generated
# browsers will warn on the Harbor UI. That is expected, not a fault. # a CA, the node pool was told at creation to trust it for exactly that
# hostname, and cert-manager signed Harbor from it. It worked for pulls,
# which is what mattered, but every browser warned on the Harbor UI and
# every docker client that was not a node needed the CA mounted by hand.
#
# Owning a domain removes all of it. harbor.infra.deployshed.com takes an
# ordinary Let's Encrypt certificate that everything already trusts — the
# nodes, dockerd in a build pod, a laptop, a browser. What retires with it:
# registry-ca-clusterissuer.yaml, registry-ca-configmap.yaml, the CA mount
# in every dind pod, and eventually the node pool's own
# private_registry_access_config block in Terraform.
#
# The nip.io name is still served, by a standalone Ingress alongside this
# one, and still signed by the private CA. It has to be: apps deployed
# before the move recorded their image as harbor.35.238.248.203.nip.io/...
# in toolshed's database, and that reference only changes when each app is
# rebuilt. Retiring the old name before then breaks their next image pull.
expose: expose:
type: ingress type: ingress
tls: tls:
enabled: true enabled: true
# secret, not the chart's "auto": auto generates its own self-signed # secret, not the chart's "auto": auto generates its own self-signed
# certificate, which the nodes have no reason to trust. This one is # certificate, which nothing has any reason to trust.
# signed by the CA they were told to trust.
certSource: secret certSource: secret
secret: secret:
secretName: harbor-tls secretName: harbor-deployshed-tls
ingress: ingress:
hosts: hosts:
core: "harbor.35.238.248.203.nip.io" core: "harbor.infra.deployshed.com"
className: contour className: contour
annotations: annotations:
# cert-manager's ingress-shim watches for this and creates the # cert-manager's ingress-shim watches for this and creates the
# Certificate itself, writing the result into the secret named # Certificate itself, writing the result into the secret named
# above. Nothing here ever touches a Certificate resource directly. # above. Nothing here ever touches a Certificate resource directly.
# The issuer is defined in devops-infra-argo-config-gcp's cert-manager.io/cluster-issuer: letsencrypt-prod
# extra-manifests/registry-ca-clusterissuer.yaml.
cert-manager.io/cluster-issuer: registry-ca-issuer # The update strategy for the two deployments with persistent volumes,
# jobservice and registry. Recreate, not the chart's default RollingUpdate,
# because their volumes are standard-rwo — ReadWriteOnce — and a rolling
# update starts the replacement pod before the old one releases the disk.
# The new pod then waits on "Multi-Attach error for volume ... already used
# by pod" forever, and the rollout never converges: this cluster sat in
# exactly that state, old pods serving while new ones hung in
# ContainerCreating. The chart's own comment on this value says to set it
# this way when RWM volumes are unavailable.
#
# The cost is honest: Harbor is briefly down during an upgrade, because the
# old pod must stop before the new one starts. That beats an update that
# cannot complete at all.
updateStrategy:
type: Recreate
# https, matching the ingress above. Harbor hands this URL to docker # https, matching the ingress above. Harbor hands this URL to docker
# clients in its own API responses, so a mismatch here breaks pushes in # clients in its own API responses, so a mismatch here breaks pushes in
# ways that look like registry errors rather than configuration. # ways that look like registry errors rather than configuration — and it is
externalURL: "https://harbor.35.238.248.203.nip.io" # what the "docker login / docker push" commands shown in Harbor's own UI
# are built from, which is where a stale value is noticed first.
externalURL: "https://harbor.infra.deployshed.com"
# From Vault through External Secrets — see # From Vault through External Secrets — see
# secretstores/harbor-admin-credentials.yaml and Vault path # secretstores/harbor-admin-credentials.yaml and Vault path
@@ -55,27 +55,23 @@ jenkins:
userKey: jenkins-admin-user userKey: jenkins-admin-user
passwordKey: jenkins-admin-password passwordKey: jenkins-admin-password
ingress: # One hostname, on the primary ingress, with its certificate.
enabled: true #
hostName: "jenkins.35.238.248.203.nip.io"
ingressClassName: contour
# This chart's primary ingress supports exactly one hostName — no # This chart's primary ingress supports exactly one hostName — no
# extraHosts like argo-cd. secondaryingress renders a whole second # extraHosts like argo-cd — so while nip.io was also served, the real
# Ingress object at the same backend, which is the supported way to get # domain lived in a whole second Ingress object (secondaryingress). That
# a second hostname here. # was the only way to keep the certificate clean, since a certificate
# covering both names is impossible: Let's Encrypt cannot issue for
# nip.io. With nip.io gone there is one name, so the second object is
# gone with it and the certificate moves onto the primary.
# #
# paths must be set explicitly: the template renders zero routes if left # controller.ingress.tls is a LIST here, not a boolean, taking an
# at the chart's own default of [], unlike the primary ingress, and the # explicit secretName — so jenkins-tls, already issued for this exact
# hostname would then answer nothing at all. # hostname by the secondary ingress, is adopted rather than reissued.
# ingress:
# Being a separate Ingress object is what makes the certificate clean —
# it covers this hostname alone, with no nip.io name to drag in.
secondaryingress:
enabled: true enabled: true
hostName: "jenkins.infra.deployshed.com" hostName: "jenkins.infra.deployshed.com"
ingressClassName: contour ingressClassName: contour
paths:
- /
annotations: annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
tls: tls:
@@ -84,11 +84,10 @@ vault:
enabled: true enabled: true
ingressClassName: contour ingressClassName: contour
annotations: annotations:
# Certificate for the real domain only; nip.io cannot have one. # Certificate for the real domain, which is now the only one served.
# nip.io could never have had one.
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
hosts: hosts:
- host: "vault.35.238.248.203.nip.io"
paths: []
- host: "vault.infra.deployshed.com" - host: "vault.infra.deployshed.com"
paths: [] paths: []
tls: tls:
@@ -56,12 +56,10 @@ victoria-metrics-single:
enabled: true enabled: true
ingressClassName: contour ingressClassName: contour
annotations: annotations:
# Certificate for the real domain only; nip.io cannot have one. # Certificate for the real domain, now the only one served. nip.io
# could never have had one.
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
hosts: hosts:
- name: vm.35.238.248.203.nip.io
path: ["/"]
port: http
- name: vm.infra.deployshed.com - name: vm.infra.deployshed.com
path: ["/"] path: ["/"]
port: http port: http