From b97b22fc01a54be85c62026327f26925ab435c15 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Fri, 19 Dec 2025 18:31:48 -0300 Subject: [PATCH] jenkins: drop helm, run via raw manifests --- services/jenkins/configmap-init-scripts.yaml | 24 ++ services/jenkins/configmap-jcasc.yaml | 165 +++++++++ services/jenkins/configmap-plugins.yaml | 17 + services/jenkins/deployment.yaml | 195 ++++++++++ services/jenkins/helmrelease.yaml | 358 ------------------- services/jenkins/ingress.yaml | 26 ++ services/jenkins/kustomization.yaml | 8 +- services/jenkins/pvc.yaml | 13 + services/jenkins/service.yaml | 18 + 9 files changed, 465 insertions(+), 359 deletions(-) create mode 100644 services/jenkins/configmap-init-scripts.yaml create mode 100644 services/jenkins/configmap-jcasc.yaml create mode 100644 services/jenkins/configmap-plugins.yaml create mode 100644 services/jenkins/deployment.yaml delete mode 100644 services/jenkins/helmrelease.yaml create mode 100644 services/jenkins/ingress.yaml create mode 100644 services/jenkins/pvc.yaml create mode 100644 services/jenkins/service.yaml diff --git a/services/jenkins/configmap-init-scripts.yaml b/services/jenkins/configmap-init-scripts.yaml new file mode 100644 index 0000000..ed87720 --- /dev/null +++ b/services/jenkins/configmap-init-scripts.yaml @@ -0,0 +1,24 @@ +# services/jenkins/configmap-init-scripts.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: jenkins-init-scripts + namespace: jenkins +data: + theme.groovy: | + import jenkins.model.Jenkins + import org.codefirst.SimpleThemeDecorator + + def instance = Jenkins.get() + def decorators = instance.getExtensionList(SimpleThemeDecorator.class) + + if (decorators?.size() > 0) { + def theme = decorators[0] + theme.setCssUrl("https://jenkins-contrib-themes.github.io/jenkins-material-theme/dist/material-ocean.css") + theme.setJsUrl("") + theme.setTheme("") + instance.save() + println("Applied simple-theme-plugin dark theme") + } else { + println("simple-theme-plugin not installed; skipping theme configuration") + } diff --git a/services/jenkins/configmap-jcasc.yaml b/services/jenkins/configmap-jcasc.yaml new file mode 100644 index 0000000..958e8a8 --- /dev/null +++ b/services/jenkins/configmap-jcasc.yaml @@ -0,0 +1,165 @@ +# services/jenkins/configmap-jcasc.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: jenkins-jcasc + namespace: jenkins +data: + securityrealm.yaml: | + jenkins: + securityRealm: + oic: + clientId: "${OIDC_CLIENT_ID}" + clientSecret: "${OIDC_CLIENT_SECRET}" + serverConfiguration: + wellKnown: + wellKnownOpenIDConfigurationUrl: "${OIDC_ISSUER}/.well-known/openid-configuration" + scopesOverride: "openid profile email" + logoutFromOpenIdProvider: true + postLogoutRedirectUrl: "https://ci.bstein.dev" + sendScopesInTokenRequest: true + rootURLFromRequest: true + userNameField: "preferred_username" + fullNameFieldName: "name" + emailFieldName: "email" + groupsFieldName: "groups" + authorization.yaml: | + jenkins: + authorizationStrategy: + loggedInUsersCanDoAnything: + allowAnonymousRead: false + creds.yaml: | + credentials: + system: + domainCredentials: + - credentials: + - usernamePassword: + scope: GLOBAL + id: gitea-pat + username: "${GITEA_PAT_USERNAME}" + password: "${GITEA_PAT_TOKEN}" + description: "Gitea PAT for pipelines" + - usernamePassword: + scope: GLOBAL + id: harbor-robot + username: "${HARBOR_ROBOT_USERNAME}" + password: "${HARBOR_ROBOT_PASSWORD}" + description: "Harbor robot for pipelines" + jobs.yaml: | + jobs: + - script: | + pipelineJob('harbor-arm-build') { + triggers { + scm('H/5 * * * *') + } + definition { + cpsScm { + scm { + git { + remote { + url('https://scm.bstein.dev/bstein/harbor-arm-build.git') + credentials('gitea-pat') + } + branches('*/master') + } + } + } + } + } + pipelineJob('ci-demo') { + triggers { + scm('H/1 * * * *') + } + definition { + cpsScm { + scm { + git { + remote { + url('https://scm.bstein.dev/bstein/ci-demo.git') + credentials('gitea-pat') + } + branches('*/master') + } + } + scriptPath('Jenkinsfile') + } + } + } + pipelineJob('bstein-dev-home') { + triggers { + scm('H/2 * * * *') + } + definition { + cpsScm { + scm { + git { + remote { + url('https://scm.bstein.dev/bstein/bstein-dev-home.git') + credentials('gitea-pat') + } + branches('*/master') + } + } + scriptPath('Jenkinsfile') + } + } + } + base.yaml: | + jenkins: + disableRememberMe: false + mode: NORMAL + numExecutors: 0 + labelString: "" + projectNamingStrategy: "standard" + markupFormatter: + plainText + clouds: + - kubernetes: + containerCapStr: "10" + connectTimeout: "5" + readTimeout: "15" + jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080" + jenkinsTunnel: "jenkins-agent.jenkins.svc.cluster.local:50000" + skipTlsVerify: false + maxRequestsPerHostStr: "32" + retentionTimeout: "5" + waitForPodSec: "600" + name: "kubernetes" + namespace: "jenkins" + restrictedPssSecurityContext: false + serverUrl: "https://kubernetes.default" + credentialsId: "" + podLabels: + - key: "jenkins/jenkins-jenkins-agent" + value: "true" + templates: + - name: "default" + namespace: "jenkins" + containers: + - name: "jnlp" + args: "^${computer.jnlpmac} ^${computer.name}" + envVars: + - envVar: + key: "JENKINS_URL" + value: "http://jenkins.jenkins.svc.cluster.local:8080/" + image: "jenkins/inbound-agent:3355.v388858a_47b_33-3" + privileged: "false" + resourceLimitCpu: 512m + resourceLimitMemory: 512Mi + resourceRequestCpu: 512m + resourceRequestMemory: 512Mi + ttyEnabled: false + workingDir: /home/jenkins/agent + idleMinutes: 0 + instanceCap: 2147483647 + label: "jenkins-jenkins-agent " + nodeUsageMode: "NORMAL" + podRetention: Never + serviceAccount: "default" + slaveConnectTimeoutStr: "100" + yamlMergeStrategy: override + inheritYamlMergeStrategy: false + slaveAgentPort: 50000 + crumbIssuer: + standard: + excludeClientIPFromCrumb: true diff --git a/services/jenkins/configmap-plugins.yaml b/services/jenkins/configmap-plugins.yaml new file mode 100644 index 0000000..eabea13 --- /dev/null +++ b/services/jenkins/configmap-plugins.yaml @@ -0,0 +1,17 @@ +# services/jenkins/configmap-plugins.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: jenkins-plugins + namespace: jenkins +data: + plugins.txt: | + kubernetes + workflow-aggregator + git + pipeline-utility-steps + configuration-as-code + configuration-as-code-support + oic-auth + job-dsl + simple-theme-plugin diff --git a/services/jenkins/deployment.yaml b/services/jenkins/deployment.yaml new file mode 100644 index 0000000..d9cf1ea --- /dev/null +++ b/services/jenkins/deployment.yaml @@ -0,0 +1,195 @@ +# services/jenkins/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jenkins + namespace: jenkins + labels: + app: jenkins +spec: + replicas: 1 + selector: + matchLabels: + app: jenkins + strategy: + type: Recreate + template: + metadata: + labels: + app: jenkins + spec: + serviceAccountName: default + nodeSelector: + kubernetes.io/arch: arm64 + node-role.kubernetes.io/worker: "true" + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 90 + preference: + matchExpressions: + - key: hardware + operator: In + values: ["rpi5"] + - weight: 50 + preference: + matchExpressions: + - key: hardware + operator: In + values: ["rpi4"] + hostAliases: + - ip: 38.28.125.112 + hostnames: + - sso.bstein.dev + securityContext: + fsGroup: 1000 + initContainers: + - name: install-plugins + image: jenkins/jenkins:2.528.3-jdk21 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + set -euo pipefail + jenkins-plugin-cli --plugin-file /plugins/plugins.txt + volumeMounts: + - name: plugins + mountPath: /plugins/plugins.txt + subPath: plugins.txt + - name: plugin-dir + mountPath: /usr/share/jenkins/ref/plugins + containers: + - name: jenkins + image: jenkins/jenkins:2.528.3-jdk21 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8080 + - name: agent-listener + containerPort: 50000 + env: + - name: JAVA_OPTS + value: "-Xms512m -Xmx2048m" + - name: JENKINS_OPTS + value: "--webroot=/var/jenkins_cache/war" + - name: JENKINS_SLAVE_AGENT_PORT + value: "50000" + - name: CASC_JENKINS_CONFIG + value: /config/jcasc + - name: ENABLE_OIDC + value: "true" + - name: OIDC_ISSUER + value: "https://sso.bstein.dev/realms/atlas" + - name: OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: jenkins-oidc + key: clientId + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: jenkins-oidc + key: clientSecret + - name: OIDC_AUTH_URL + valueFrom: + secretKeyRef: + name: jenkins-oidc + key: authorizationUrl + - name: OIDC_TOKEN_URL + valueFrom: + secretKeyRef: + name: jenkins-oidc + key: tokenUrl + - name: OIDC_USERINFO_URL + valueFrom: + secretKeyRef: + name: jenkins-oidc + key: userInfoUrl + - name: OIDC_LOGOUT_URL + valueFrom: + secretKeyRef: + name: jenkins-oidc + key: logoutUrl + - name: HARBOR_ROBOT_USERNAME + valueFrom: + secretKeyRef: + name: harbor-robot-creds + key: username + - name: HARBOR_ROBOT_PASSWORD + valueFrom: + secretKeyRef: + name: harbor-robot-creds + key: password + - name: GITEA_PAT_USERNAME + valueFrom: + secretKeyRef: + name: gitea-pat + key: username + - name: GITEA_PAT_TOKEN + valueFrom: + secretKeyRef: + name: gitea-pat + key: token + resources: + requests: + cpu: 750m + memory: 1536Mi + limits: + cpu: 1500m + memory: 3Gi + livenessProbe: + httpGet: + path: /login + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + readinessProbe: + httpGet: + path: /login + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + startupProbe: + httpGet: + path: /login + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 20 + volumeMounts: + - name: jenkins-home + mountPath: /var/jenkins_home + - name: jenkins-cache + mountPath: /var/jenkins_cache + - name: jcasc + mountPath: /config/jcasc + - name: init-scripts + mountPath: /usr/share/jenkins/ref/init.groovy.d + - name: plugin-dir + mountPath: /usr/share/jenkins/ref/plugins + - name: tmp + mountPath: /tmp + volumes: + - name: jenkins-home + persistentVolumeClaim: + claimName: jenkins + - name: jenkins-cache + emptyDir: {} + - name: plugin-dir + emptyDir: {} + - name: plugins + configMap: + name: jenkins-plugins + - name: jcasc + configMap: + name: jenkins-jcasc + - name: init-scripts + configMap: + name: jenkins-init-scripts + - name: tmp + emptyDir: {} diff --git a/services/jenkins/helmrelease.yaml b/services/jenkins/helmrelease.yaml deleted file mode 100644 index b2bff90..0000000 --- a/services/jenkins/helmrelease.yaml +++ /dev/null @@ -1,358 +0,0 @@ -# services/jenkins/helmrelease.yaml -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: jenkins - namespace: jenkins -spec: - interval: 30m - chart: - spec: - chart: jenkins - version: 5.8.114 - sourceRef: - kind: HelmRepository - name: jenkins - namespace: flux-system - install: - timeout: 15m - remediation: - retries: 3 - upgrade: - timeout: 15m - remediation: - retries: 3 - remediateLastFailure: true - cleanupOnFail: true - rollback: - timeout: 15m - values: - controller: - nodeSelector: - kubernetes.io/arch: arm64 - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/arch - operator: In - values: ["arm64"] - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 90 - preference: - matchExpressions: - - key: hardware - operator: In - values: ["rpi5"] - - weight: 50 - preference: - matchExpressions: - - key: hardware - operator: In - values: ["rpi4"] - resources: - requests: - cpu: 750m - memory: 1.5Gi - limits: - cpu: 1500m - memory: 3Gi - javaOpts: "-Xms512m -Xmx2048m" - startupProbe: - httpGet: - path: /login - port: http - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 20 - jenkinsUrl: https://ci.bstein.dev - ingress: - enabled: true - hostName: ci.bstein.dev - ingressClassName: traefik - annotations: - cert-manager.io/cluster-issuer: letsencrypt - traefik.ingress.kubernetes.io/router.entrypoints: websecure - tls: - - secretName: jenkins-tls - hosts: - - ci.bstein.dev - hostAliases: - - ip: 38.28.125.112 - hostnames: - - sso.bstein.dev - installPlugins: - - kubernetes - - workflow-aggregator - - git - - pipeline-utility-steps - - configuration-as-code - - oic-auth - - job-dsl - - configuration-as-code-support - - simple-theme-plugin - containerEnv: - - name: ENABLE_OIDC - value: "true" - - name: OIDC_ISSUER - value: "https://sso.bstein.dev/realms/atlas" - - name: OIDC_CLIENT_ID - valueFrom: - secretKeyRef: - name: jenkins-oidc - key: clientId - - name: OIDC_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: jenkins-oidc - key: clientSecret - - name: OIDC_AUTH_URL - valueFrom: - secretKeyRef: - name: jenkins-oidc - key: authorizationUrl - - name: OIDC_TOKEN_URL - valueFrom: - secretKeyRef: - name: jenkins-oidc - key: tokenUrl - - name: OIDC_USERINFO_URL - valueFrom: - secretKeyRef: - name: jenkins-oidc - key: userInfoUrl - - name: OIDC_LOGOUT_URL - valueFrom: - secretKeyRef: - name: jenkins-oidc - key: logoutUrl - - name: HARBOR_ROBOT_USERNAME - valueFrom: - secretKeyRef: - name: harbor-robot-creds - key: username - - name: HARBOR_ROBOT_PASSWORD - valueFrom: - secretKeyRef: - name: harbor-robot-creds - key: password - - name: GITEA_PAT_USERNAME - valueFrom: - secretKeyRef: - name: gitea-pat - key: username - - name: GITEA_PAT_TOKEN - valueFrom: - secretKeyRef: - name: gitea-pat - key: token - customInitContainers: - - name: clean-jcasc-stale - image: alpine:3.20 - imagePullPolicy: IfNotPresent - command: - - sh - - -c - - | - set -euo pipefail - rm -f /var/jenkins_home/casc_configs/* || true - securityContext: - runAsNonRoot: true - runAsUser: 1000 - runAsGroup: 1000 - volumeMounts: - - name: jenkins-home - mountPath: /var/jenkins_home - initScripts: - theme.groovy: | - import jenkins.model.Jenkins - import org.codefirst.SimpleThemeDecorator - - def instance = Jenkins.get() - def decorators = instance.getExtensionList(SimpleThemeDecorator.class) - - if (decorators?.size() > 0) { - def theme = decorators[0] - theme.setCssUrl("https://jenkins-contrib-themes.github.io/jenkins-material-theme/dist/material-ocean.css") - theme.setJsUrl("") - theme.setTheme("") - instance.save() - println("Applied simple-theme-plugin dark theme") - } else { - println("simple-theme-plugin not installed; skipping theme configuration") - } - JCasC: - defaultConfig: false - securityRealm: | - oic: - clientId: "${OIDC_CLIENT_ID}" - clientSecret: "${OIDC_CLIENT_SECRET}" - serverConfiguration: - wellKnown: - wellKnownOpenIDConfigurationUrl: "${OIDC_ISSUER}/.well-known/openid-configuration" - scopesOverride: "openid profile email" - logoutFromOpenIdProvider: true - postLogoutRedirectUrl: "https://ci.bstein.dev" - sendScopesInTokenRequest: true - rootURLFromRequest: true - userNameField: "preferred_username" - fullNameFieldName: "name" - emailFieldName: "email" - groupsFieldName: "groups" - authorizationStrategy: | - loggedInUsersCanDoAnything: - allowAnonymousRead: false - configScripts: - base.yaml: | - jenkins: - disableRememberMe: false - mode: NORMAL - numExecutors: 0 - labelString: "" - projectNamingStrategy: "standard" - markupFormatter: - plainText - clouds: - - kubernetes: - containerCapStr: "10" - defaultsProviderTemplate: "" - connectTimeout: "5" - readTimeout: "15" - jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080" - jenkinsTunnel: "jenkins-agent.jenkins.svc.cluster.local:50000" - skipTlsVerify: false - usageRestricted: false - maxRequestsPerHostStr: "32" - retentionTimeout: "5" - waitForPodSec: "600" - name: "kubernetes" - namespace: "jenkins" - restrictedPssSecurityContext: false - serverUrl: "https://kubernetes.default" - credentialsId: "" - podLabels: - - key: "jenkins/jenkins-jenkins-agent" - value: "true" - templates: - - name: "default" - namespace: "jenkins" - id: a23c9bbcd21e360a77d51b426f05bd7b8032d8fdedd6ffb97c436883ce6c5ffa - containers: - - name: "jnlp" - alwaysPullImage: false - args: "^${computer.jnlpmac} ^${computer.name}" - envVars: - - envVar: - key: "JENKINS_URL" - value: "http://jenkins.jenkins.svc.cluster.local:8080/" - image: "jenkins/inbound-agent:3355.v388858a_47b_33-3" - privileged: "false" - resourceLimitCpu: 512m - resourceLimitMemory: 512Mi - resourceRequestCpu: 512m - resourceRequestMemory: 512Mi - ttyEnabled: false - workingDir: /home/jenkins/agent - idleMinutes: 0 - instanceCap: 2147483647 - label: "jenkins-jenkins-agent " - nodeUsageMode: "NORMAL" - podRetention: Never - showRawYaml: true - serviceAccount: "default" - slaveConnectTimeoutStr: "100" - yamlMergeStrategy: override - inheritYamlMergeStrategy: false - slaveAgentPort: 50000 - crumbIssuer: - standard: - excludeClientIPFromCrumb: true - security: - apiToken: - creationOfLegacyTokenEnabled: false - tokenGenerationOnCreationEnabled: false - usageStatisticsEnabled: true - creds.yaml: | - credentials: - system: - domainCredentials: - - credentials: - - usernamePassword: - scope: GLOBAL - id: gitea-pat - username: "${GITEA_PAT_USERNAME}" - password: "${GITEA_PAT_TOKEN}" - description: "Gitea PAT for pipelines" - - usernamePassword: - scope: GLOBAL - id: harbor-robot - username: "${HARBOR_ROBOT_USERNAME}" - password: "${HARBOR_ROBOT_PASSWORD}" - description: "Harbor robot for pipelines" - jobs.yaml: | - jobs: - - script: | - pipelineJob('harbor-arm-build') { - triggers { - scm('H/5 * * * *') - } - definition { - cpsScm { - scm { - git { - remote { - url('https://scm.bstein.dev/bstein/harbor-arm-build.git') - credentials('gitea-pat') - } - branches('*/master') - } - } - } - } - } - pipelineJob('ci-demo') { - triggers { - scm('H/1 * * * *') - } - definition { - cpsScm { - scm { - git { - remote { - url('https://scm.bstein.dev/bstein/ci-demo.git') - credentials('gitea-pat') - } - branches('*/master') - } - } - scriptPath('Jenkinsfile') - } - } - } - pipelineJob('bstein-dev-home') { - triggers { - scm('H/2 * * * *') - } - definition { - cpsScm { - scm { - git { - remote { - url('https://scm.bstein.dev/bstein/bstein-dev-home.git') - credentials('gitea-pat') - } - branches('*/master') - } - } - scriptPath('Jenkinsfile') - } - } - } - persistence: - enabled: true - storageClass: astreae - size: 50Gi - serviceAccount: - create: true diff --git a/services/jenkins/ingress.yaml b/services/jenkins/ingress.yaml new file mode 100644 index 0000000..e702c8c --- /dev/null +++ b/services/jenkins/ingress.yaml @@ -0,0 +1,26 @@ +# services/jenkins/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: jenkins + namespace: jenkins + annotations: + cert-manager.io/cluster-issuer: letsencrypt + traefik.ingress.kubernetes.io/router.entrypoints: websecure +spec: + ingressClassName: traefik + tls: + - secretName: jenkins-tls + hosts: + - ci.bstein.dev + rules: + - host: ci.bstein.dev + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: jenkins + port: + name: http diff --git a/services/jenkins/kustomization.yaml b/services/jenkins/kustomization.yaml index b20b1d3..a0bd200 100644 --- a/services/jenkins/kustomization.yaml +++ b/services/jenkins/kustomization.yaml @@ -4,4 +4,10 @@ kind: Kustomization namespace: jenkins resources: - namespace.yaml - - helmrelease.yaml + - pvc.yaml + - configmap-jcasc.yaml + - configmap-init-scripts.yaml + - configmap-plugins.yaml + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/services/jenkins/pvc.yaml b/services/jenkins/pvc.yaml new file mode 100644 index 0000000..049df32 --- /dev/null +++ b/services/jenkins/pvc.yaml @@ -0,0 +1,13 @@ +# services/jenkins/pvc.yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: jenkins + namespace: jenkins +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi + storageClassName: astreae diff --git a/services/jenkins/service.yaml b/services/jenkins/service.yaml new file mode 100644 index 0000000..5fba878 --- /dev/null +++ b/services/jenkins/service.yaml @@ -0,0 +1,18 @@ +# services/jenkins/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: jenkins + namespace: jenkins + labels: + app: jenkins +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + - name: agent-listener + port: 50000 + targetPort: 50000 + selector: + app: jenkins