diff --git a/.gitignore b/.gitignore index 0fa3ceeda..d6d24bd26 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,11 @@ *.log ansible.cfg kubeconfig +*.decrypted~* +.decrypted~secret.yaml +nautobot_secrets.yml +generated_certificate/ +key.txt +roles/install-cert-manager/templates/prod-byrnbaker-me-prod.j2 +.gitignore +kube-prometheus-stack/ \ No newline at end of file diff --git a/README.md b/README.md index 36e3e8101..33387c835 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,45 @@ After watching a couple Techno Tims excelent videos on k3s, I decided to add a c ## Here is a list of Roles I have added to Techno Tims k3s-ansible project - [Building VMs from an Ubuntu cloud-init template in Proxmox](https://technotim.live/posts/cloud-init-cloud-image/) - Destroying the VMs and deleting from Proxmox +- [Deploy cert manager on k3s and configuring staging and production certificates](https://technotim.live/posts/kube-traefik-cert-manager-le/) - [Deploying Traefik on to k3s](https://technotim.live/posts/kube-traefik-cert-manager-le/) -- Deploying Nautobot on the k3s -- [Deploying cert manager on k3s and configuring staging and production certificates](https://technotim.live/posts/kube-traefik-cert-manager-le/) +- Deploy Nautobot on the k3s +- Deploy Rancher UI 2.8 with the rancherLB - still trying to figure out how to get this working behind traefik. +- Deploy a self hosted gitlab behind traefik. + + +## Building VMs from an Ubuntu cloud-init template in Proxmox & Destroying the VMs and deleting from Proxmox +This includes some additional task of mounting NFS, or a ISCSI target. I am using the ISCSI for longhorn. This makes it easy to tear everything down and rebuild it quickly incase you hose up your cluster. + +## Deploy cert manager on k3s and configuring staging and production certificates +Uses tags to just install Cert-manager via helm, as well as tags for staging (For testing all components) and production ( when you are ready to rollout your production wildcard certificates). + +## Deploying Traefik +This just takes what TechnoTim showed on his video, and customizes it a little bit to suit my needs, including enabling it for Gitlab. ## Nautobot -I use this tool pretty extensivly with work and my home lab. If you do not know about it you should check out this project managed by [NetworktoCode](https://docs.nautobot.com/projects/core/en/stable/) +I use this tool pretty extensively with work and my home lab. If you do not know about it you should check out this project managed by [NetworktoCode](https://docs.nautobot.com/projects/core/en/stable/) -## Things I moved around +## Rancher UI +In case you want to have the UI, I use it for navigating around and checking logs and deployments. Also use it to install Longhorn as well, would like to convert that over to a ansible task as well, but after I figure out how to get Rancher UI behind traefik. +## Gitlab +I like the idea of self hosting my code, so I thought it would be fun to have a local gitlab instance running. This way I can mess around with gitlab-ci and following along with [technotim](https://technotim.live/posts/self-hosted-devops-stack/). Includes ingress for both staging and production certificates. +After installation completes use the following to get your initial root password - ```kubectl get secret gitlab-gitlab-initial-root-password -n gitlab -ojsonpath='{.data.password}' | base64 --decode ; echo``` -## Thanks 🤝 +## Installing everything +You can use the ```deploy.sh``` bash script and it will install everything listed above. -This repo is really standing on the shoulders of giants. Thank you to all those who have contributed and thanks to these repos for code and ideas: +## If you install with staging it is easy to switch to your production certificate +You can use ```switch_deployment_to_production.sh``` bash script. This will flip all of the services installed to the real certificate from LetsEncrypt. +## If you would to destroy the entire deployment +To destroy the entire deployment you can use the ```destroy-k3s-vms.yml```. This will stop and remove the Proxmox VMs. + + + +## Thanks 🤝 +This repo is really standing on the shoulders of giants. Thank you to all those who have contributed and thanks to these repos for code and ideas: - [k3s-io/k3s-ansible](https://github.com/k3s-io/k3s-ansible) - [geerlingguy/turing-pi-cluster](https://github.com/geerlingguy/turing-pi-cluster) - [212850a/k3s-ansible](https://github.com/212850a/k3s-ansible) diff --git a/collections/requirements.yml b/collections/requirements.yml index d9ab54c6a..bdce1c9c3 100644 --- a/collections/requirements.yml +++ b/collections/requirements.yml @@ -6,3 +6,4 @@ collections: - name: kubernetes.core - name: ansible.netcommon - name: community.crypto + - name: ansibleguy.opnsense diff --git a/deploy-k3s-vms.yml b/deploy-k3s-vms.yml index d9d7aef17..0c152a1a9 100644 --- a/deploy-k3s-vms.yml +++ b/deploy-k3s-vms.yml @@ -2,6 +2,94 @@ - name: Prepare Proxmox VM Cluster hosts: localhost gather_facts: true + + vars_prompt: + - name: node + prompt: What Prox node do you want to deploy on? + private: false + - name: template_id + prompt: What Prox template do you want to use (Prox Template VMID)? + private: false + roles: - - role: proxmox_vm - when: prox_api is defined \ No newline at end of file + - role: deploy_proxmox_vm + when: prox_api is defined + +- name: Create and Mount NFS share to VMs + hosts: node + gather_facts: true + + tasks: + - name: Install qemu-guest-agent, nfs-common, and open-iscsi + ansible.builtin.apt: + name: + - qemu-guest-agent + - nfs-common + - open-iscsi + state: present + update_cache: true + become: true + + - block: + - name: Enable and start open-iscsi + ansible.builtin.systemd: + name: open-iscsi + state: started + enabled: yes + become: true + + - block: + - name: Ensure mount directory exists + ansible.builtin.file: + path: /mnt/longhorn/data + state: directory + become: true + + - name: Ensure NFS share is mounted + ansible.posix.mount: + path: /mnt/longhorn/data + src: "{{ nfs_mount }}" + fstype: nfs + opts: defaults + state: mounted + become: true + when: nfs_mount is defined + + - block: + - name: Discover iscsi targets + command: iscsiadm -m discovery -t st -p {{ iscsi_host }} + become: true + + - name: Login to iscsi target + command: iscsiadm -m node --targetname {{ hostvars[inventory_hostname]['iscsi_target'] }} --portal {{ iscsi_host }}:3260 --login + become: true + + - name: Format the disk + ansible.builtin.filesystem: + fstype: ext4 + dev: /dev/sdb + become: true + + - name: Create directory + file: + path: /mnt/iscsi + state: directory + mode: '0755' + become: true + + - name: Mount the disk + mount: + path: /mnt/iscsi + src: /dev/sdb + fstype: ext4 + state: mounted + opts: _netdev + become: true + + - name: Add mount to fstab + lineinfile: + path: /etc/fstab + line: '/dev/sdb /mnt/iscsi ext4 _netdev 0 0' + state: present + become: true + when: hostvars[inventory_hostname]['iscsi_target'] is defined diff --git a/deploy.sh b/deploy.sh index 8f702d6e8..316204842 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,3 +1,17 @@ #!/bin/bash +echo "What Prox node do you want to deploy on?" +read node +echo "What Prox template do you want to use (Prox Template VMID)?" +read template_id + +echo "Do you want a staging installation or a production installation? (Enter 'staging-install' or 'production-install')" +read installation_type + +ansible-playbook deploy-k3s-vms.yml -e "node=$node template_id=$template_id" ansible-playbook site.yml +ansible-playbook install-traefik.yml --tags "$installation_type" +ansible-playbook install-cert-manager.yml --tags "$installation_type" +ansible-playbook install-rancher-ui.yml --tags "$installation_type" + +ansible-playbook install-opnsense-host-overrides.yml \ No newline at end of file diff --git a/destroy-k3s-vms.yml b/destroy-k3s-vms.yml index ed9e7dd94..5d9ccf6cb 100644 --- a/destroy-k3s-vms.yml +++ b/destroy-k3s-vms.yml @@ -2,6 +2,12 @@ - name: Prepare Proxmox VM Cluster hosts: localhost gather_facts: true + + vars_prompt: + - name: node + prompt: What Prox node do you want to remove the VMs from? + private: false + roles: - role: destroy_proxmox_vm when: prox_api is defined \ No newline at end of file diff --git a/environment_check.sh b/environment_check.sh new file mode 100644 index 000000000..f7d57feb1 --- /dev/null +++ b/environment_check.sh @@ -0,0 +1,537 @@ +#!/bin/bash + +NVME_CLI_VERSION="1.12" + +###################################################### +# Log +###################################################### +export RED='\x1b[0;31m' +export GREEN='\x1b[38;5;22m' +export CYAN='\x1b[36m' +export YELLOW='\x1b[33m' +export NO_COLOR='\x1b[0m' + +if [ -z "${LOG_TITLE}" ]; then + LOG_TITLE='' +fi +if [ -z "${LOG_LEVEL}" ]; then + LOG_LEVEL="INFO" +fi + +debug() { + if [[ "${LOG_LEVEL}" == "DEBUG" ]]; then + local log_title + if [ -n "${LOG_TITLE}" ]; then + log_title="(${LOG_TITLE})" + else + log_title='' + fi + echo -e "${GREEN}[DEBUG]${log_title} ${NO_COLOR}$1" + fi +} + +info() { + if [[ "${LOG_LEVEL}" == "DEBUG" ]] ||\ + [[ "${LOG_LEVEL}" == "INFO" ]]; then + local log_title + if [ -n "${LOG_TITLE}" ]; then + log_title="(${LOG_TITLE})" + else + log_title='' + fi + echo -e "${CYAN}[INFO] ${log_title} ${NO_COLOR}$1" + fi +} + +warn() { + if [[ "${LOG_LEVEL}" == "DEBUG" ]] ||\ + [[ "${LOG_LEVEL}" == "INFO" ]] ||\ + [[ "${LOG_LEVEL}" == "WARN" ]]; then + local log_title + if [ -n "${LOG_TITLE}" ]; then + log_title="(${LOG_TITLE})" + else + log_title='' + fi + echo -e "${YELLOW}[WARN] ${log_title} ${NO_COLOR}$1" + fi +} + +error() { + if [[ "${LOG_LEVEL}" == "DEBUG" ]] ||\ + [[ "${LOG_LEVEL}" == "INFO" ]] ||\ + [[ "${LOG_LEVEL}" == "WARN" ]] ||\ + [[ "${LOG_LEVEL}" == "ERROR" ]]; then + local log_title + if [ -n "${LOG_TITLE}" ]; then + log_title="(${LOG_TITLE})" + else + log_title='' + fi + echo -e "${RED}[ERROR]${log_title} ${NO_COLOR}$1" + fi +} + +###################################################### +# Check logics +###################################################### +set_packages_and_check_cmd() { + case $OS in + *"debian"* | *"ubuntu"* ) + CHECK_CMD='dpkg -l | grep -w' + PACKAGES=(nfs-common open-iscsi) + ;; + *"centos"* | *"fedora"* | *"rocky"* | *"ol"* ) + CHECK_CMD='rpm -q' + PACKAGES=(nfs-utils iscsi-initiator-utils) + ;; + *"suse"* ) + CHECK_CMD='rpm -q' + PACKAGES=(nfs-client open-iscsi) + ;; + *"arch"* ) + CHECK_CMD='pacman -Q' + PACKAGES=(nfs-utils open-iscsi) + ;; + *"gentoo"* ) + CHECK_CMD='qlist -I' + PACKAGES=(net-fs/nfs-utils sys-block/open-iscsi) + ;; + *) + CHECK_CMD='' + PACKAGES=() + warn "Stop the environment check because '$OS' is not supported in the environment check script." + exit 1 + ;; + esac +} + +detect_node_kernel_release() { + local pod="$1" + + KERNEL_RELEASE=$(kubectl exec $pod -- nsenter --mount=/proc/1/ns/mnt -- bash -c 'uname -r') + echo "$KERNEL_RELEASE" +} + +detect_node_os() { + local pod="$1" + + OS=$(kubectl exec $pod -- nsenter --mount=/proc/1/ns/mnt -- bash -c 'grep -E "^ID_LIKE=" /etc/os-release | cut -d= -f2') + if [[ -z "${OS}" ]]; then + OS=$(kubectl exec $pod -- nsenter --mount=/proc/1/ns/mnt -- bash -c 'grep -E "^ID=" /etc/os-release | cut -d= -f2') + fi + echo "$OS" +} + +check_local_dependencies() { + local targets=($@) + + local all_found=true + for ((i=0; i<${#targets[@]}; i++)); do + local target=${targets[$i]} + if [ "$(which $target)" = "" ]; then + all_found=false + error "Not found: $target" + fi + done + + if [ "$all_found" = "false" ]; then + msg="Please install missing dependencies: ${targets[@]}." + info "$msg" + exit 2 + fi + + msg="Required dependencies '${targets[@]}' are installed." + info "$msg" +} + +create_ds() { +cat < $TEMP_DIR/environment_check.yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app: longhorn-environment-check + name: longhorn-environment-check +spec: + selector: + matchLabels: + app: longhorn-environment-check + template: + metadata: + labels: + app: longhorn-environment-check + spec: + hostPID: true + containers: + - name: longhorn-environment-check + image: alpine:3.12 + args: ["/bin/sh", "-c", "sleep 1000000000"] + volumeMounts: + - name: mountpoint + mountPath: /tmp/longhorn-environment-check + mountPropagation: Bidirectional + securityContext: + privileged: true + volumes: + - name: mountpoint + hostPath: + path: /tmp/longhorn-environment-check +EOF + kubectl create -f $TEMP_DIR/environment_check.yaml > /dev/null +} + +cleanup() { + info "Cleaning up longhorn-environment-check pods..." + kubectl delete -f $TEMP_DIR/environment_check.yaml > /dev/null + rm -rf $TEMP_DIR + info "Cleanup completed." +} + +wait_ds_ready() { + while true; do + local ds=$(kubectl get ds/longhorn-environment-check -o json) + local numberReady=$(echo $ds | jq .status.numberReady) + local desiredNumberScheduled=$(echo $ds | jq .status.desiredNumberScheduled) + + if [ "$desiredNumberScheduled" = "$numberReady" ] && [ "$desiredNumberScheduled" != "0" ]; then + info "All longhorn-environment-check pods are ready ($numberReady/$desiredNumberScheduled)." + return + fi + + info "Waiting for longhorn-environment-check pods to become ready ($numberReady/$desiredNumberScheduled)..." + sleep 3 + done +} + +check_mount_propagation() { + local allSupported=true + local pods=$(kubectl -l app=longhorn-environment-check get po -o json) + + local ds=$(kubectl get ds/longhorn-environment-check -o json) + local desiredNumberScheduled=$(echo $ds | jq .status.desiredNumberScheduled) + + for ((i=0; i"; then + deduplicate_hostnames+=("${hostname}") + fi + done + + if [ "${#deduplicate_hostnames[@]}" != "${num_nodes}" ]; then + error "Nodes do not have unique hostnames." + exit 2 + fi + + info "All nodes have unique hostnames." +} + +check_nodes() { + local name=$1 + local callback=$2 + shift + shift + + info "Checking $name..." + + local all_passed=true + + local pods=$(kubectl get pods -o name -l app=longhorn-environment-check) + for pod in ${pods}; do + eval "${callback} ${pod} $@" + if [ $? -ne 0 ]; then + all_passed=false + fi + done + + if [ "$all_passed" = "false" ]; then + return 1 + fi +} + +verlte() { + printf '%s\n' "$1" "$2" | sort -C -V +} + +verlt() { + ! verlte "$2" "$1" +} + +check_kernel_release() { + local pod=$1 + + recommended_kernel_release="5.8" + + local kernel=$(detect_node_kernel_release ${pod}) + + if verlt "$kernel" "$recommended_kernel_release" ; then + local node=$(kubectl get ${pod} --no-headers -o=custom-columns=:.spec.nodeName) + warn "Node $node has outdated kernel release: $kernel. Recommending kernel release >= $recommended_kernel_release" + return 1 + fi +} + +check_iscsid() { + local pod=$1 + + kubectl exec ${pod} -- nsenter --mount=/proc/1/ns/mnt -- bash -c "systemctl status --no-pager iscsid.service" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + kubectl exec ${pod} -- nsenter --mount=/proc/1/ns/mnt -- bash -c "systemctl status --no-pager iscsid.socket" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + node=$(kubectl get ${pod} --no-headers -o=custom-columns=:.spec.nodeName) + error "Neither iscsid.service nor iscsid.socket is not running on ${node}" + return 1 + fi + fi +} + +check_multipathd() { + local pod=$1 + + kubectl exec $pod -- nsenter --mount=/proc/1/ns/mnt -- bash -c "systemctl status --no-pager multipathd.service" > /dev/null 2>&1 + if [ $? = 0 ]; then + node=$(kubectl get ${pod} --no-headers -o=custom-columns=:.spec.nodeName) + warn "multipathd is running on ${node}" + return 1 + fi +} + +check_packages() { + local pod=$1 + + OS=$(detect_node_os ${pod}) + if [ x"$OS" = x"" ]; then + error "Failed to detect OS on node ${node}" + return 1 + fi + + set_packages_and_check_cmd + + for ((i=0; i<${#PACKAGES[@]}; i++)); do + check_package ${PACKAGES[$i]} + if [ $? -ne 0 ]; then + return 1 + fi + done +} + +check_package() { + local package=$1 + + kubectl exec $pod -- nsenter --mount=/proc/1/ns/mnt -- timeout 30 bash -c "$CHECK_CMD $package" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + node=$(kubectl get ${pod} --no-headers -o=custom-columns=:.spec.nodeName) + error "$package is not found in $node." + return 1 + fi +} + +check_nfs_client() { + local pod=$1 + local node=$(kubectl get ${pod} --no-headers -o=custom-columns=:.spec.nodeName) + + local options=("CONFIG_NFS_V4_2" "CONFIG_NFS_V4_1" "CONFIG_NFS_V4") + + local kernel=$(detect_node_kernel_release ${pod}) + if [ "x${kernel}" = "x" ]; then + warn "Failed to check NFS client installation, because unable to detect kernel release on node ${node}" + return 1 + fi + + for option in "${options[@]}"; do + kubectl exec ${pod} -- nsenter --mount=/proc/1/ns/mnt -- bash -c "[ -f /boot/config-${kernel} ]" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + warn "Failed to check $option on node ${node}, because /boot/config-${kernel} does not exist on node ${node}" + continue + fi + + check_kernel_module ${pod} ${option} nfs + if [ $? = 0 ]; then + return 0 + fi + done + + error "NFS clients ${options[*]} should be enabled at least one." + return 1 +} + +check_kernel_module() { + local pod=$1 + local option=$2 + local module=$3 + + local kernel=$(detect_node_kernel_release ${pod}) + if [ "x${kernel}" = "x" ]; then + warn "Failed to check kernel config option ${option}, because unable to detect kernel release on node ${node}" + return 1 + fi + + kubectl exec ${pod} -- nsenter --mount=/proc/1/ns/mnt -- bash -c "[ -e /boot/config-${kernel} ]" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + warn "Failed to check kernel config option ${option}, because /boot/config-${kernel} does not exist on node ${node}" + return 1 + fi + + value=$(kubectl exec ${pod} -- nsenter --mount=/proc/1/ns/mnt -- bash -c "grep "^$option=" /boot/config-${kernel} | cut -d= -f2") + if [ -z "${value}" ]; then + error "Failed to find kernel config $option on node ${node}" + return 1 + elif [ "${value}" = "m" ]; then + kubectl exec ${pod} -- nsenter --mount=/proc/1/ns/mnt -- bash -c "lsmod | grep ${module}" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + node=$(kubectl get ${pod} --no-headers -o=custom-columns=:.spec.nodeName) + error "kernel module ${module} is not enabled on ${node}" + return 1 + fi + elif [ "${value}" = "y" ]; then + return 0 + else + warn "Unknown value for $option: $value" + return 1 + fi +} + +check_hugepage() { + local pod=$1 + local expected_nr_hugepages=$2 + + nr_hugepages=$(kubectl exec ${pod} -- nsenter --mount=/proc/1/ns/mnt -- bash -c 'cat /proc/sys/vm/nr_hugepages') + if [ $? -ne 0 ]; then + error "Failed to check hugepage size on node ${node}" + return 1 + fi + + if [ $nr_hugepages -lt $expected_nr_hugepages ]; then + error "Hugepage size is not enough on node ${node}. Expected: ${expected_nr_hugepages}, Actual: ${nr_hugepages}" + return 1 + fi +} + +function check_sse42_support() { + local pod=$1 + + node=$(kubectl get ${pod} --no-headers -o=custom-columns=:.spec.nodeName) + + machine=$(kubectl exec $pod -- nsenter --mount=/proc/1/ns/mnt -- bash -c 'uname -m' 2>/dev/null) + if [ $? -ne 0 ]; then + error "Failed to check machine on node ${node}" + return 1 + fi + + if [ "$machine" = "x86_64" ]; then + sse42_support=$(kubectl exec $pod -- nsenter --mount=/proc/1/ns/mnt -- bash -c 'grep -o sse4_2 /proc/cpuinfo | wc -l' 2>/dev/null) + if [ $? -ne 0 ]; then + error "Failed to check SSE4.2 instruction set on node ${node}" + return 1 + fi + + if [ "$sse42_support" -ge 1 ]; then + return 0 + fi + + error "CPU does not support SSE4.2" + return 1 + else + warn "Skip SSE4.2 instruction set check on node ${node} because it is not x86_64" + fi +} + +function show_help() { + cat <=0.10.1 pre-commit>=3.6.0 pre-commit-hooks>=4.5.0 pyyaml>=6.0.1 +httpx diff --git a/roles/cert-manager/tasks/main.yml b/roles/cert-manager/tasks/main.yml deleted file mode 100644 index f40774da3..000000000 --- a/roles/cert-manager/tasks/main.yml +++ /dev/null @@ -1,109 +0,0 @@ ---- -- name: Add jetstack Helm repository - community.kubernetes.helm_repository: - name: jetstack - repo_url: https://charts.jetstack.io - -- name: Update Helm jetstack repositories - community.kubernetes.helm_repository: - name: jetstack - repo_url: https://charts.jetstack.io - force_update: yes - -- name: Create cert-manager namespace - community.kubernetes.k8s: - api_version: v1 - kind: Namespace - name: cert-manager - state: present - -- name: Apply cert-manager CRDs - community.kubernetes.k8s: - state: present - src: https://github.com/cert-manager/cert-manager/releases/download/v1.14.1/cert-manager.crds.yaml - -- name: Install cert-manager Helm chart - community.kubernetes.helm: - name: cert-manager - chart_ref: jetstack/cert-manager - release_namespace: cert-manager - values: "{{ cert_manager }}" - chart_version: 1.14.1 - state: present - -- name: Apply issuers secrets - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'secret-cf-token.j2') }}" - -- name: Apply Staging ClusterIssuer - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'letsencrypt-staging.j2') }}" - -- name: Apply Generate Stagging Certificate - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'stage-local-byrnbaker-me.j2') }}" - -- name: Get challenges - community.kubernetes.k8s_info: - kind: Challenge - namespace: default - register: result - until: result.resources | length == 0 - retries: 20 - delay: 30 - -- name: Get a cert from nautobot - community.crypto.get_certificate: - host: "nautobot.local.byrnbaker.me" - port: 443 - run_once: true - register: cert - -- name: How many days until cert expires - ansible.builtin.debug: - msg: "cert expires in: {{ expire_days }} days. Cert issued by {{ cert.issuer.O }}. Cert covers {{ cert.subject.CN }}." - vars: - expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}" - -- block: - - name: Apply Production ClusterIssuer - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'letsencrypt-prod.j2') }}" - - - name: Apply Production Certificate - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'prod-local-byrnbaker-me.j2') }}" - when: cert.issuer.O == "(STAGING) Let's Encrypt" - -- name: Get challenges - community.kubernetes.k8s_info: - kind: Challenge - namespace: default - register: result - until: result.resources | length == 0 - retries: 20 - delay: 30 - -- name: Apply Traefik-Nautobot ingress production certificate - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'prod-nautobot-ingress.j2') }}" - register: ingress_output - -- name: Get a cert from nautobot - community.crypto.get_certificate: - host: "nautobot.local.byrnbaker.me" - port: 443 - run_once: true - register: cert - -- name: How many days until cert expires - ansible.builtin.debug: - msg: "cert expires in: {{ expire_days }} days. Cert issued by {{ cert.issuer.O }}. Cert covers {{ cert.subject.CN }}." - vars: - expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}" diff --git a/roles/cert-manager/templates/prod-local-byrnbaker-me.j2 b/roles/cert-manager/templates/prod-local-byrnbaker-me.j2 deleted file mode 100644 index 0d3577237..000000000 --- a/roles/cert-manager/templates/prod-local-byrnbaker-me.j2 +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: local-byrnbaker-me - namespace: default -spec: - secretName: local-byrnbaker-me-tls - issuerRef: - name: letsencrypt-production - kind: ClusterIssuer - commonName: "*.local.byrnbaker.me" - dnsNames: - - "local.byrnbaker.me" - - "*.local.byrnbaker.me" \ No newline at end of file diff --git a/roles/cert-manager/templates/stage-local-byrnbaker-me.j2 b/roles/cert-manager/templates/stage-local-byrnbaker-me.j2 deleted file mode 100644 index e3b690ccb..000000000 --- a/roles/cert-manager/templates/stage-local-byrnbaker-me.j2 +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: local-byrnbaker-me - namespace: default -spec: - secretName: local-byrnbaker-me-staging-tls - issuerRef: - name: letsencrypt-staging - kind: ClusterIssuer - commonName: "*.local.byrnbaker.me" - dnsNames: - - "local.byrnbaker.me" - - "*.local.byrnbaker.me" diff --git a/roles/proxmox_vm/tasks/build_vms.yml b/roles/deploy_proxmox_vm/tasks/build_vms.yml similarity index 65% rename from roles/proxmox_vm/tasks/build_vms.yml rename to roles/deploy_proxmox_vm/tasks/build_vms.yml index 52ce3ab69..8134a2a8d 100644 --- a/roles/proxmox_vm/tasks/build_vms.yml +++ b/roles/deploy_proxmox_vm/tasks/build_vms.yml @@ -5,22 +5,13 @@ - set_fact: vmid: "{{ hostvars[target_host]['vmid'] }}" + storage: "{{ hostvars[target_host]['storage'] }}" + vcpus: "{{ hostvars[target_host]['vcpus'] }}" + memory: "{{ hostvars[target_host]['memory'] }}" -# - name: Get next VMID from Proxmox -# uri: -# url: "{{prox_api}}cluster/nextid" -# method: GET -# headers: -# Authorization: "{{ prox_auth }}" -# validate_certs: no -# register: nextid - -# - set_fact: -# vmid: "{{ nextid.json.data }}" - -- name: Clone the Ubuntu VM Template +- name: Clone the Ubuntu VM Template for {{ target_host }} uri: - url: "{{prox_api}}nodes/hp-pve01/qemu/8000/clone" + url: "{{prox_api}}nodes/{{node}}/qemu/{{template_id}}/clone" method: POST headers: Authorization: "{{ prox_auth }}" @@ -30,13 +21,13 @@ newid: "{{ vmid }}" full: true name: "{{ target_host }}" - storage: "hp-pve-ssd-500gb" + storage: "{{ storage }}" validate_certs: no register: create_vm -- name: Wait for VM cloning to finish +- name: Wait for {{ target_host }} VM cloning to finish uri: - url: "{{prox_api}}nodes/hp-pve01/qemu/{{ vmid }}/status/current" + url: "{{prox_api}}nodes/{{node}}/qemu/{{ vmid }}/status/current" method: GET headers: Authorization: "{{ prox_auth }}" @@ -53,17 +44,17 @@ - set_fact: vm_gateway: "{{ vm_network | ansible.utils.ipaddr('address') | ipmath(1) }}" -- name: Update the clones IP to match the inventory +- name: Update {{ target_host }} vm IP to match the inventory uri: - url: "{{ prox_api }}nodes/hp-pve01/qemu/{{ vmid }}/config" + url: "{{ prox_api }}nodes/{{node}}/qemu/{{ vmid }}/config" method: PUT headers: Authorization: "{{ prox_auth }}" Content-Type: "application/json" body_format: json body: - cores: 4 - memory: 4096 + cores: "{{ vcpus|int }}" + memory: "{{ memory|int }}" ipconfig0: "ip={{ vm_ip }},gw={{ vm_gateway }}" ciuser: "{{ ansible_user }}" cipassword: "{{ ansible_pass }}" @@ -72,9 +63,9 @@ validate_certs: no register: modify_vm -- name: Expanding the bootdisk +- name: Expanding the bootdisk on {{ target_host }} uri: - url: "{{ prox_api }}nodes/hp-pve01/qemu/{{ vmid }}/resize" + url: "{{ prox_api }}nodes/{{node}}/qemu/{{ vmid }}/resize" method: PUT headers: Authorization: "{{ prox_auth }}" @@ -86,13 +77,14 @@ validate_certs: no register: expand_bootdisk -- name: Start VM +- name: Start {{ target_host }} uri: - url: "{{prox_api}}nodes/hp-pve01/qemu/{{ vmid }}/status/start" + url: "{{prox_api}}nodes/{{node}}/qemu/{{ vmid }}/status/start" method: POST headers: Authorization: "{{ prox_auth }}" Content-Type: "application/json" body: "{}" validate_certs: no - register: start_vm \ No newline at end of file + register: start_vm + \ No newline at end of file diff --git a/roles/deploy_proxmox_vm/tasks/main.yml b/roles/deploy_proxmox_vm/tasks/main.yml new file mode 100644 index 000000000..635ecc13f --- /dev/null +++ b/roles/deploy_proxmox_vm/tasks/main.yml @@ -0,0 +1,21 @@ +--- +- name: Include tasks for each host in k3s_cluster + include_tasks: build_vms.yml + loop: "{{ groups['k3s_cluster'] }}" + loop_control: + loop_var: target_host + vars: + host_ip: "{{ hostvars[target_host]['ansible_host'] }}" + +- name: Check if VMs are available + ansible.builtin.wait_for: + host: "{{ host_ip }}" + port: 22 + state: started + delay: 10 + timeout: 300 + loop: "{{ groups['k3s_cluster'] }}" + loop_control: + loop_var: target_host + vars: + host_ip: "{{ hostvars[target_host]['ansible_host'] }}" \ No newline at end of file diff --git a/roles/destroy_proxmox_vm/tasks/destroy_vms.yml b/roles/destroy_proxmox_vm/tasks/destroy_vms.yml index 9b009cf57..85b4de9cd 100644 --- a/roles/destroy_proxmox_vm/tasks/destroy_vms.yml +++ b/roles/destroy_proxmox_vm/tasks/destroy_vms.yml @@ -8,21 +8,35 @@ - name: Stop VM uri: - url: "{{prox_api}}nodes/hp-pve01/qemu/{{ vmid }}/status/stop" + url: "{{prox_api}}nodes/{{node}}/qemu/{{ vmid }}/status/stop" method: POST headers: Authorization: "{{ prox_auth }}" Content-Type: "application/json" body: "{}" validate_certs: no - register: stop_vm + +- name: Check that VM has stopped + uri: + url: "{{prox_api}}nodes/{{node}}/qemu/{{ vmid }}/status/current" + method: GET + headers: + Authorization: "{{ prox_auth }}" + Content-Type: "application/json" + validate_certs: no + register: stopped_vm + until: stopped_vm.json.data.status == "stopped" + retries: 20 + delay: 5 + - name: Destroy VM uri: - url: "{{prox_api}}nodes/hp-pve01/qemu/{{ vmid }}" + url: "{{prox_api}}nodes/{{node}}/qemu/{{ vmid }}" method: DELETE headers: Authorization: "{{ prox_auth }}" Content-Type: "application/json" validate_certs: no - register: delete_vm \ No newline at end of file + register: delete_vm + when: stopped_vm.json.data.status == "stopped" \ No newline at end of file diff --git a/roles/install-cert-manager/tasks/main.yml b/roles/install-cert-manager/tasks/main.yml new file mode 100644 index 000000000..5a9a83202 --- /dev/null +++ b/roles/install-cert-manager/tasks/main.yml @@ -0,0 +1,249 @@ +--- +### Installing Cert Manager +- block: + - name: Add jetstack Helm repository + kubernetes.core.helm_repository: + name: jetstack + repo_url: https://charts.jetstack.io + + - name: Update Helm jetstack repositories + kubernetes.core.helm_repository: + name: jetstack + repo_url: https://charts.jetstack.io + force_update: yes + + - name: Create cert-manager namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: cert-manager + state: present + + - name: Apply cert-manager CRDs + kubernetes.core.k8s: + state: present + src: "https://github.com/cert-manager/cert-manager/releases/download/v{{certmanager_version}}/cert-manager.crds.yaml" + + - name: Install cert-manager Helm chart + kubernetes.core.helm: + name: cert-manager + chart_ref: jetstack/cert-manager + release_namespace: cert-manager + values: "{{ cert_manager }}" + chart_version: "{{ certmanager_version }}" + state: present + + - name: Apply issuers secrets + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'secret-cf-token.j2') }}" + + - name: Add emberstack Helm repository + kubernetes.core.helm_repository: + name: emberstack + repo_url: https://emberstack.github.io/helm-charts + + - name: Update Helm repository + kubernetes.core.helm_repository: + name: emberstack + repo_url: https://emberstack.github.io/helm-charts + force_update: yes + + - name: Install reflector Helm chart + kubernetes.core.helm: + name: reflector + chart_ref: emberstack/reflector + release_namespace: default + state: present + + - name: Apply Staging ClusterIssuer + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'letsencrypt-staging.j2') }}" + + - name: Generate Staging Certificate + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'stage-local-byrnbaker-me.j2') }}" + + - name: Get challenges + kubernetes.core.k8s_info: + kind: Challenge + namespace: default + register: result + until: result.resources | length == 0 + retries: 20 + delay: 30 + + - name: Capture the certificate for use later + shell: | + kubectl get secret {{ staging_secret }} -n default -o jsonpath="{.data.tls\.crt}" | base64 --decode > ./generated_certificate/staging-tls.crt + - name: Capture the certificate for use later + shell: | + kubectl get secret {{ staging_secret }} -n default -o jsonpath="{.data.tls\.key}" | base64 --decode > ./generated_certificate/staging-tls.key + tags: staging-install + +### Production install +- block: + - name: Add jetstack Helm repository + kubernetes.core.helm_repository: + name: jetstack + repo_url: https://charts.jetstack.io + + - name: Update Helm jetstack repositories + kubernetes.core.helm_repository: + name: jetstack + repo_url: https://charts.jetstack.io + force_update: yes + + - name: Create cert-manager namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: cert-manager + state: present + + - name: Apply cert-manager CRDs + kubernetes.core.k8s: + state: present + src: "https://github.com/cert-manager/cert-manager/releases/download/v{{certmanager_version}}/cert-manager.crds.yaml" + + - name: Install cert-manager Helm chart + kubernetes.core.helm: + name: cert-manager + chart_ref: jetstack/cert-manager + release_namespace: cert-manager + values: "{{ cert_manager }}" + chart_version: "{{ certmanager_version }}" + state: present + + - name: Apply issuers secrets + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'secret-cf-token.j2') }}" + + - name: Add emberstack Helm repository + kubernetes.core.helm_repository: + name: emberstack + repo_url: https://emberstack.github.io/helm-charts + + - name: Update Helm repository + kubernetes.core.helm_repository: + name: emberstack + repo_url: https://emberstack.github.io/helm-charts + force_update: yes + + - name: Install reflector Helm chart + kubernetes.core.helm: + name: reflector + chart_ref: emberstack/reflector + release_namespace: default + state: present + + - name: Apply Production ClusterIssuer + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'letsencrypt-prod.j2') }}" + + - name: Apply Production Certificate + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'prod-local-byrnbaker-me.j2') }}" + + - name: Get challenges + kubernetes.core.k8s_info: + kind: Challenge + namespace: default + register: result + until: result.resources | length == 0 + retries: 20 + delay: 30 + + - name: Capture the certificate for use later + shell: | + kubectl get secret {{ production_secret }} -n default -o jsonpath="{.data.tls\.crt}" | base64 --decode > ./generated_certificate/production-tls.crt + - name: Capture the certificate for use later + shell: | + kubectl get secret {{ production_secret }} -n default -o jsonpath="{.data.tls\.key}" | base64 --decode > ./generated_certificate/production-tls.key + tags: production-install + +- block: + - name: Generate Staging Certificate + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'stage-blog-byrnbaker-me.j2') }}" + + - name: Get challenges + kubernetes.core.k8s_info: + kind: Challenge + namespace: default + register: result + until: result.resources | length == 0 + retries: 20 + delay: 30 + + - name: Capture the certificate for use later + shell: | + kubectl get secret staging-blog-{{ root_domain_secret }}-tls -n default -o jsonpath="{.data.tls\.crt}" | base64 --decode > ./generated_certificate/staging-blog-{{ root_domain_secret }}-tls.crt + - name: Capture the certificate for use later + shell: | + kubectl get secret staging-blog-{{ root_domain_secret }}-tls -n default -o jsonpath="{.data.tls\.key}" | base64 --decode > ./generated_certificate/staging-blog-{{ root_domain_secret }}-tls.key + tags: staging-blog-tls + +- block: + - name: Generate production Certificate + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'prod-blog-byrnbaker-me.j2') }}" + + - name: Get challenges + kubernetes.core.k8s_info: + kind: Challenge + namespace: default + register: result + until: result.resources | length == 0 + retries: 20 + delay: 30 + + - name: Capture the certificate for use later + shell: | + kubectl get secret prod-blog-{{ root_domain_secret }}-tls -n default -o jsonpath="{.data.tls\.crt}" | base64 --decode > ./generated_certificate/prod-blog-{{ root_domain_secret }}-tls.crt + - name: Capture the certificate for use later + shell: | + kubectl get secret prod-blog-{{ root_domain_secret }}-tls -n default -o jsonpath="{.data.tls\.key}" | base64 --decode > ./generated_certificate/prod-blog-{{ root_domain_secret }}-tls.key + tags: prod-blog-tls + +- block: + # - name: Apply service-key and issuer + # kubernetes.core.k8s: + # state: present + # src: + # - "{{ lookup('template', 'cf_prod_issuer.j2') }}" + # - "{{ lookup('template', 'service_key.j2') }}" + + # - name: Apply Production Certificate + # kubernetes.core.k8s: + # state: present + # definition: "{{ lookup('template', 'cf_origin_cert.j2') }}" + + - name: Apply Production Certificate + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'prod-byrnbaker-me.j2') }}" + + - name: Get challenges + kubernetes.core.k8s_info: + kind: Challenge + namespace: default + register: result + until: result.resources | length == 0 + retries: 20 + delay: 30 + + - name: Capture the certificate for use later + shell: | + kubectl get secret {{ root_domain_secret }} -n default -o jsonpath="{.data.tls\.crt}" | base64 --decode > ./generated_certificate/byrnbaker-wildcard-tls.crt + - name: Capture the certificate for use later + shell: | + kubectl get secret {{ root_domain_secret }} -n default -o jsonpath="{.data.tls\.key}" | base64 --decode > ./generated_certificate/byrnbaker-wildcard-tls.key + tags: root-tls \ No newline at end of file diff --git a/roles/install-cert-manager/templates/cf_origin_cert.j2 b/roles/install-cert-manager/templates/cf_origin_cert.j2 new file mode 100644 index 000000000..82a23f695 --- /dev/null +++ b/roles/install-cert-manager/templates/cf_origin_cert.j2 @@ -0,0 +1,26 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: byrnbaker-me + namespace: default +spec: + # The secret name where cert-manager should store the signed certificate + secretName: byrnbaker-me-tls + dnsNames: + - blog.{{ public_domain }} + # Duration of the certificate + duration: 168h + # Renew a day before the certificate expiration + renewBefore: 24h + # Reference the Origin CA Issuer you created above, which must be in the same namespace. + issuerRef: + group: cert-manager.k8s.cloudflare.com + kind: OriginIssuer + name: prod-issuer + # Reflects certificate into other namespaces + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "traefik,gitlab,cattle-system,nautobot" \ No newline at end of file diff --git a/roles/install-cert-manager/templates/cf_prod_issuer.j2 b/roles/install-cert-manager/templates/cf_prod_issuer.j2 new file mode 100644 index 000000000..435e63e9a --- /dev/null +++ b/roles/install-cert-manager/templates/cf_prod_issuer.j2 @@ -0,0 +1,12 @@ +--- +apiVersion: cert-manager.k8s.cloudflare.com/v1 +kind: OriginIssuer +metadata: + name: prod-issuer + namespace: default +spec: + requestType: OriginECC + auth: + serviceKeyRef: + name: service-key + key: key \ No newline at end of file diff --git a/roles/cert-manager/templates/letsencrypt-prod.j2 b/roles/install-cert-manager/templates/letsencrypt-prod.j2 similarity index 77% rename from roles/cert-manager/templates/letsencrypt-prod.j2 rename to roles/install-cert-manager/templates/letsencrypt-prod.j2 index e18d48250..b143a7b3c 100644 --- a/roles/cert-manager/templates/letsencrypt-prod.j2 +++ b/roles/install-cert-manager/templates/letsencrypt-prod.j2 @@ -6,16 +6,15 @@ metadata: spec: acme: server: https://acme-v02.api.letsencrypt.org/directory - email: byrn.baker@gmail.com + email: {{ cf_email }} privateKeySecretRef: name: letsencrypt-production solvers: - dns01: cloudflare: - email: byrn.baker@gmail.com + email: {{ cf_email }} apiTokenSecretRef: name: cloudflare-token-secret key: cloudflare-token selector: - dnsZones: - - "byrnbaker.me" \ No newline at end of file + dnsZones: {{ cf_zones }} \ No newline at end of file diff --git a/roles/cert-manager/templates/letsencrypt-staging.j2 b/roles/install-cert-manager/templates/letsencrypt-staging.j2 similarity index 77% rename from roles/cert-manager/templates/letsencrypt-staging.j2 rename to roles/install-cert-manager/templates/letsencrypt-staging.j2 index 3c3bb24aa..8d8be4f5a 100644 --- a/roles/cert-manager/templates/letsencrypt-staging.j2 +++ b/roles/install-cert-manager/templates/letsencrypt-staging.j2 @@ -6,16 +6,15 @@ metadata: spec: acme: server: https://acme-staging-v02.api.letsencrypt.org/directory - email: byrn.baker@gmail.com + email: {{ cf_email }} privateKeySecretRef: name: letsencrypt-staging solvers: - dns01: cloudflare: - email: byrn.baker@gmail.com + email: {{ cf_email }} apiTokenSecretRef: name: cloudflare-token-secret key: cloudflare-token selector: - dnsZones: - - "byrnbaker.me" \ No newline at end of file + dnsZones: {{ cf_zones }} \ No newline at end of file diff --git a/roles/install-cert-manager/templates/prod-blog-byrnbaker-me.j2 b/roles/install-cert-manager/templates/prod-blog-byrnbaker-me.j2 new file mode 100644 index 000000000..870699f97 --- /dev/null +++ b/roles/install-cert-manager/templates/prod-blog-byrnbaker-me.j2 @@ -0,0 +1,20 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: prod-blog-{{ root_domain_secret }} + namespace: default +spec: + secretName: prod-blog-{{ root_domain_secret }}-tls + commonName: "blog.{{ public_domain }}" + dnsNames: + - "blog.{{ public_domain }}" + - "www.blog.{{ public_domain }}" + issuerRef: + name: letsencrypt-production + kind: ClusterIssuer + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "traefik,gitlab,cattle-system,nautobot" \ No newline at end of file diff --git a/roles/install-cert-manager/templates/prod-byrnbaker-me.j2 b/roles/install-cert-manager/templates/prod-byrnbaker-me.j2 new file mode 100644 index 000000000..5bd981f65 --- /dev/null +++ b/roles/install-cert-manager/templates/prod-byrnbaker-me.j2 @@ -0,0 +1,18 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: byrnbaker-me + namespace: default +spec: + secretName: {{ root_domain_secret }} + commonName: "*.{{ public_domain }}" + dnsNames: {{ root_dnsnames }} + issuerRef: + name: letsencrypt-production + kind: ClusterIssuer + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "traefik,gitlab,cattle-system,nautobot" \ No newline at end of file diff --git a/roles/install-cert-manager/templates/prod-local-byrnbaker-me.j2 b/roles/install-cert-manager/templates/prod-local-byrnbaker-me.j2 new file mode 100644 index 000000000..cf70bf95b --- /dev/null +++ b/roles/install-cert-manager/templates/prod-local-byrnbaker-me.j2 @@ -0,0 +1,18 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: local-byrnbaker-me + namespace: default +spec: + secretName: {{ production_secret }} + commonName: "{{ commonname }}" + dnsNames: {{ dnsnames }} + issuerRef: + name: letsencrypt-production + kind: ClusterIssuer + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "traefik,gitlab,cattle-system,nautobot" \ No newline at end of file diff --git a/roles/cert-manager/templates/prod-nautobot-ingress.j2 b/roles/install-cert-manager/templates/prod-nautobot-ingress.j2 similarity index 89% rename from roles/cert-manager/templates/prod-nautobot-ingress.j2 rename to roles/install-cert-manager/templates/prod-nautobot-ingress.j2 index 4d365d9e6..5864846d7 100644 --- a/roles/cert-manager/templates/prod-nautobot-ingress.j2 +++ b/roles/install-cert-manager/templates/prod-nautobot-ingress.j2 @@ -10,7 +10,7 @@ spec: entryPoints: - websecure routes: - - match: Host(`nautobot.local.byrnbaker.me`) + - match: Host(`{{ nb_url }}`) kind: Rule services: - name: nautobot-default diff --git a/roles/cert-manager/templates/secret-cf-token.j2 b/roles/install-cert-manager/templates/secret-cf-token.j2 similarity index 100% rename from roles/cert-manager/templates/secret-cf-token.j2 rename to roles/install-cert-manager/templates/secret-cf-token.j2 diff --git a/roles/install-cert-manager/templates/service_key.j2 b/roles/install-cert-manager/templates/service_key.j2 new file mode 100644 index 000000000..684456df2 --- /dev/null +++ b/roles/install-cert-manager/templates/service_key.j2 @@ -0,0 +1,8 @@ +apiVersion: v1 +data: + key: {{ cf_origin_ca_key }} +kind: Secret +metadata: + creationTimestamp: null + name: service-key + namespace: default \ No newline at end of file diff --git a/roles/install-cert-manager/templates/stage-blog-byrnbaker-me.j2 b/roles/install-cert-manager/templates/stage-blog-byrnbaker-me.j2 new file mode 100644 index 000000000..2f3485ec8 --- /dev/null +++ b/roles/install-cert-manager/templates/stage-blog-byrnbaker-me.j2 @@ -0,0 +1,20 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: staging-blog-{{ root_domain_secret }} + namespace: default +spec: + secretName: staging-blog-{{ root_domain_secret }}-tls + commonName: "blog.{{ public_domain }}" + dnsNames: + - "blog.{{ public_domain }}" + - "www.blog.{{ public_domain }}" + issuerRef: + name: letsencrypt-staging + kind: ClusterIssuer + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "traefik,gitlab,cattle-system,nautobot" diff --git a/roles/install-cert-manager/templates/stage-local-byrnbaker-me.j2 b/roles/install-cert-manager/templates/stage-local-byrnbaker-me.j2 new file mode 100644 index 000000000..18c46350a --- /dev/null +++ b/roles/install-cert-manager/templates/stage-local-byrnbaker-me.j2 @@ -0,0 +1,18 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: staging-local-byrnbaker-me + namespace: default +spec: + secretName: {{ staging_secret }} + commonName: "{{ commonname }}" + dnsNames: {{ dnsnames }} + issuerRef: + name: letsencrypt-staging + kind: ClusterIssuer + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "traefik,gitlab,cattle-system,nautobot" diff --git a/roles/cert-manager/vars/main.yml b/roles/install-cert-manager/vars/main.yml similarity index 100% rename from roles/cert-manager/vars/main.yml rename to roles/install-cert-manager/vars/main.yml diff --git a/roles/install-gitlab/tasks/main.yml b/roles/install-gitlab/tasks/main.yml new file mode 100644 index 000000000..3807913f9 --- /dev/null +++ b/roles/install-gitlab/tasks/main.yml @@ -0,0 +1,99 @@ +--- +- block: + - name: Add gitlab Helm repository + kubernetes.core.helm_repository: + name: gitlab + repo_url: https://charts.gitlab.io/ + + - name: Update Helm repositories + kubernetes.core.helm_repository: + name: gitlab + repo_url: https://charts.gitlab.io/ + force_update: yes + + - name: Create gitlab namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: gitlab + state: present + + - name: Install gitlab-staging Helm chart + kubernetes.core.helm: + name: gitlab + chart_ref: gitlab/gitlab + release_namespace: gitlab + values: "{{ staging_gitlab_values }}" + state: present + + - name: Apply default headers + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'default-headers.j2') }}" + + - name: Apply Traefik-Gitlab staging ingress + community.kubernetes.k8s: + state: present + definition: "{{ lookup('template', 'staging-ingress.j2') }}" + register: ingress_output + tags: staging-install + +- block: + - name: Add gitlab Helm repository + kubernetes.core.helm_repository: + name: gitlab + repo_url: https://charts.gitlab.io/ + + - name: Update Helm repositories + kubernetes.core.helm_repository: + name: gitlab + repo_url: https://charts.gitlab.io/ + force_update: yes + + - name: Create gitlab namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: gitlab + state: present + + - name: Install gitlab-production Helm chart + kubernetes.core.helm: + name: gitlab + chart_ref: gitlab/gitlab + release_namespace: gitlab + values: "{{ production_gitlab_values }}" + state: present + + - name: Apply default headers + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'default-headers.j2') }}" + + - name: Generate production Certificate + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'gitlab-byrnbaker-me.j2') }}" + + - name: Get challenges + kubernetes.core.k8s_info: + kind: Challenge + namespace: default + register: result + until: result.resources | length == 0 + retries: 20 + delay: 30 + + - name: Capture the certificate for use later + shell: | + kubectl get secret gitlab-{{ root_domain_secret }}-tls -n default -o jsonpath="{.data.tls\.crt}" | base64 --decode > ./generated_certificate/gitlab-{{ root_domain_secret }}-tls.crt + - name: Capture the certificate for use later + shell: | + kubectl get secret gitlab-{{ root_domain_secret }}-tls -n default -o jsonpath="{.data.tls\.key}" | base64 --decode > ./generated_certificate/gitlab-{{ root_domain_secret }}-tls.key + + - name: Apply Traefik-Gitlab production ingress + community.kubernetes.k8s: + state: present + definition: "{{ lookup('template', 'prod-ingress.j2') }}" + register: ingress_output + tags: production-install \ No newline at end of file diff --git a/roles/install-gitlab/templates/default-headers.j2 b/roles/install-gitlab/templates/default-headers.j2 new file mode 100644 index 000000000..9b2f123ef --- /dev/null +++ b/roles/install-gitlab/templates/default-headers.j2 @@ -0,0 +1,16 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: default-headers + namespace: gitlab +spec: + headers: + browserXssFilter: true + contentTypeNosniff: true + forceSTSHeader: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 15552000 + customFrameOptionsValue: SAMEORIGIN + customRequestHeaders: + X-Forwarded-Proto: https \ No newline at end of file diff --git a/roles/install-gitlab/templates/gitlab-byrnbaker-me.j2 b/roles/install-gitlab/templates/gitlab-byrnbaker-me.j2 new file mode 100644 index 000000000..3f021e4b0 --- /dev/null +++ b/roles/install-gitlab/templates/gitlab-byrnbaker-me.j2 @@ -0,0 +1,20 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: gitlab-{{ root_domain_secret }} + namespace: default +spec: + secretName: gitlab-{{ root_domain_secret }}-tls + commonName: "gitlab.{{ public_domain }}" + dnsNames: + - "gitlab.{{ public_domain }}" + - "www.gitlab.{{ public_domain }}" + issuerRef: + name: letsencrypt-production + kind: ClusterIssuer + secretTemplate: + annotations: + reflector.v1.k8s.emberstack.com/reflection-allowed: "true" + reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" + reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "traefik,gitlab,cattle-system,nautobot" \ No newline at end of file diff --git a/roles/install-gitlab/templates/prod-ingress.j2 b/roles/install-gitlab/templates/prod-ingress.j2 new file mode 100644 index 000000000..79363cd80 --- /dev/null +++ b/roles/install-gitlab/templates/prod-ingress.j2 @@ -0,0 +1,106 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: gitlab + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external + +spec: + entryPoints: + - websecure + - web + routes: + - match: Host(`gitlab.{{ public_domain }}`) && PathPrefix(`/admin/sidekiq`) + kind: Rule + services: + - name: gitlab-webservice-default + port: 8080 + - match: Host(`gitlab.{{ public_domain }}`) + kind: Rule + services: + - name: gitlab-webservice-default + port: 8181 + middlewares: + - name: default-headers + namespace: gitlab + tls: + secretName: gitlab-{{ root_domain_secret }}-tls +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: minio + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external + +spec: + entryPoints: + - websecure + routes: + - match: Host(`minio.{{ public_domain }}`) + kind: Rule + services: + - name: gitlab-minio-svc + port: 9000 + tls: + secretName: gitlab-{{ root_domain_secret }}-tls +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: registry + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external + +spec: + entryPoints: + - websecure + routes: + - match: Host(`registry.{{ public_domain }}`) + kind: Rule + services: + - name: gitlab-registry + port: 5000 + tls: + secretName: gitlab-{{ root_domain_secret }}-tls +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: kas + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external + +spec: + entryPoints: + - websecure + routes: + - match: Host(`kas.{{ public_domain }}`) + kind: Rule + services: + - name: gitlab-kas + port: 8150 + tls: + secretName: gitlab-{{ root_domain_secret }}-tls +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: gitlab-ssh + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - gitlab-ssh + routes: + - match: HostSNI(`*`) + kind: Rule + services: + - name: gitlab-gitlab-shell + port: 22 \ No newline at end of file diff --git a/roles/install-gitlab/templates/staging-ingress.j2 b/roles/install-gitlab/templates/staging-ingress.j2 new file mode 100644 index 000000000..d262fdb8a --- /dev/null +++ b/roles/install-gitlab/templates/staging-ingress.j2 @@ -0,0 +1,83 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: gitlab + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + - web + routes: + - match: Host(`gitlab.{{ install_domain }}`) && PathPrefix(`/admin/sidekiq`) + kind: Rule + services: + - name: gitlab-webservice-default + port: 8080 + - match: Host(`gitlab.{{ install_domain }}`) + kind: Rule + services: + - name: gitlab-webservice-default + port: 8181 + middlewares: + - name: default-headers + namespace: gitlab + tls: + secretName: {{ staging_secret }} +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: minio + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`minio.{{ install_domain }}`) + kind: Rule + services: + - name: gitlab-minio-svc + port: 9000 + tls: + secretName: {{ staging_secret }} +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: registry + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`registry.{{ install_domain }}`) + kind: Rule + services: + - name: gitlab-registry + port: 5000 + tls: + secretName: {{ staging_secret }} +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: gitlab-ssh + namespace: gitlab + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - gitlab-ssh + routes: + - match: HostSNI(`*`) + kind: Rule + services: + - name: gitlab-gitlab-shell + port: 22 \ No newline at end of file diff --git a/roles/install-gitlab/vars/main.yml b/roles/install-gitlab/vars/main.yml new file mode 100644 index 000000000..93e727d87 --- /dev/null +++ b/roles/install-gitlab/vars/main.yml @@ -0,0 +1,114 @@ +staging_gitlab_values: + global: + hosts: + domain: "{{ commonname | replace('*.', '') }}" + edition: ce + ingress: + enabled: true + provider: traefik + class: traefik-external + annotations: + kubernetes.io/tls-acme: true + traefik.ingress.kubernetes.io/router.tls: "true" + traefik.ingress.kubernetes.io/router.entrypoints: websecure + configureCertmanager: false + + certmanager: + install: false + certmanager-issuer: + email: "{{ cf_email }}" + nginx-ingress: + enabled: false + postgresql: + image: + tag: 13.6.0 + persistence: + enabled: true + gitaly: + enabled: true + storageClassName: my-gitaly-storage + + # CUSTOM - Required for separately managed certmanager + registry: + ingress: + tls: + secretName: "{{ staging_secret }}" + minio: + ingress: + tls: + secretName: "{{ staging_secret }}" + gitlab: + migrations: + initialRootPassword: + key: "admin" + gitlab-pages: + ingress: + tls: + secretName: "{{ staging_secret }}" + webservice: + ingress: + path: + tls: + secretName: "{{ staging_secret }}" + toolbox: + replicas: 1 + antiAffinityLabels: + matchLabels: + app: 'gitaly' + +production_gitlab_values: + global: + hosts: + domain: "{{ public_domain }}" + edition: ce + ingress: + enabled: true + provider: traefik + class: traefik-external + annotations: + kubernetes.io/tls-acme: true + traefik.ingress.kubernetes.io/router.tls: "true" + traefik.ingress.kubernetes.io/router.entrypoints: websecure + configureCertmanager: false + certmanager: + install: false + certmanager-issuer: + email: "{{ cf_email }}" + nginx-ingress: + enabled: false + postgresql: + image: + tag: 13.6.0 + persistence: + enabled: true + gitaly: + enabled: true + storageClassName: my-gitaly-storage + + # CUSTOM - Required for separately managed certmanager + registry: + ingress: + tls: + secretName: "gitlab-{{ root_domain_secret }}-tls" + minio: + ingress: + tls: + secretName: "gitlab-{{ root_domain_secret }}-tls" + gitlab: + migrations: + initialRootPassword: + key: "admin" + gitlab-pages: + ingress: + tls: + secretName: "gitlab-{{ root_domain_secret }}-tls" + webservice: + ingress: + path: + tls: + secretName: "gitlab-{{ root_domain_secret }}-tls" + toolbox: + replicas: 1 + antiAffinityLabels: + matchLabels: + app: 'gitaly' \ No newline at end of file diff --git a/roles/install-longhorn/tasks/main.yml b/roles/install-longhorn/tasks/main.yml new file mode 100644 index 000000000..a0328568d --- /dev/null +++ b/roles/install-longhorn/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- block: + - name: Add longhorn Helm repository + kubernetes.core.helm_repository: + name: longhorn + repo_url: https://charts.longhorn.io + ignore_errors: true + + - name: Update Helm repositories + kubernetes.core.helm_repository: + name: longhorn + repo_url: https://charts.longhorn.io/ + force_update: yes + + - name: Create longhorn namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: longhorn-system + state: present + + - name: Install longhorn Helm chart + kubernetes.core.helm: + name: longhorn + chart_ref: longhorn/longhorn + release_namespace: longhorn-system + values: "{{ staging_longhorn_values }}" + chart_version: 1.6.0 + state: present + + # - name: Apply Production Traefik ingress for Longhorn UI + # kubernetes.core.k8s: + # state: present + # definition: "{{ lookup('template', 'staging-ingress.j2') }}" + tags: staging-install + +- block: + - name: Add longhorn Helm repository + kubernetes.core.helm_repository: + name: longhorn + repo_url: https://charts.longhorn.io + + - name: Update Helm repositories + kubernetes.core.helm_repository: + name: longhorn + repo_url: https://charts.longhorn.io/ + force_update: yes + + - name: Create longhorn namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: longhorn-system + state: present + + - name: Install longhorn Helm chart + kubernetes.core.helm: + name: longhorn + chart_ref: longhorn/longhorn + release_namespace: longhorn-system + values: "{{ production_longhorn_values }}" + chart_version: 1.6.0 + state: present + + - name: Apply Production Traefik ingress for Longhorn UI + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'production-ingress.j2') }}" + tags: production-install \ No newline at end of file diff --git a/roles/install-longhorn/templates/production-ingress.j2 b/roles/install-longhorn/templates/production-ingress.j2 new file mode 100644 index 000000000..1969cf7be --- /dev/null +++ b/roles/install-longhorn/templates/production-ingress.j2 @@ -0,0 +1,18 @@ +--- +kind: IngressRoute +apiVersion: traefik.containo.us/v1alpha1 +metadata: + name: longhorn-frontend + namespace: longhorn-system + +spec: + entryPoints: + - websecure + routes: + - match: Host(`longhorn.{{ install_domain }}`) + kind: Rule + services: + - name: longhorn-frontend + port: 443 + tls: + secretName: {{ production_secret }} \ No newline at end of file diff --git a/roles/install-longhorn/templates/staging-ingress.j2 b/roles/install-longhorn/templates/staging-ingress.j2 new file mode 100644 index 000000000..b7b873b8a --- /dev/null +++ b/roles/install-longhorn/templates/staging-ingress.j2 @@ -0,0 +1,18 @@ +--- +kind: IngressRoute +apiVersion: traefik.containo.us/v1alpha1 +metadata: + name: longhorn-frontend + namespace: longhorn-system + +spec: + entryPoints: + - web + routes: + - match: Host(`longhorn.{{ install_domain }}`) + kind: Rule + services: + - name: longhorn-frontend + port: 80 + tls: + secretName: {{ staging_secret }} \ No newline at end of file diff --git a/roles/install-longhorn/vars/main.yml b/roles/install-longhorn/vars/main.yml new file mode 100644 index 000000000..a3c05ac5a --- /dev/null +++ b/roles/install-longhorn/vars/main.yml @@ -0,0 +1,10 @@ +--- +staging_longhorn_values: + defaultSettings: + defaultReplicaCount: 2 + backupTarget: "nfs://192.168.130.55://volume1/longhorn" + +production_longhorn_values: + defaultSettings: + defaultReplicaCount: 2 + backupTarget: "nfs://192.168.130.55://volume1/longhorn" \ No newline at end of file diff --git a/roles/install-nautobot/tasks/main.yml b/roles/install-nautobot/tasks/main.yml new file mode 100644 index 000000000..b6b1c3a4b --- /dev/null +++ b/roles/install-nautobot/tasks/main.yml @@ -0,0 +1,98 @@ +--- +- block: + - name: Add Nautobot Helm repository + community.kubernetes.helm_repository: + name: nautobot + repo_url: https://nautobot.github.io/helm-charts/ + + - name: Update Helm Nautobot repositories + community.kubernetes.helm_repository: + name: nautobot + repo_url: https://nautobot.github.io/helm-charts/ + force_update: yes + + - name: Install Nautobot Helm chart + community.kubernetes.helm: + name: nautobot + chart_ref: nautobot/nautobot + release_namespace: default + values: "{{ nautobot_values }}" + state: present + register: nautobot_output + + - name: Apply Traefik-Nautobot ingress + community.kubernetes.k8s: + state: present + definition: "{{ lookup('template', 'staging-ingress.j2') }}" + register: ingress_output + + - name: Run command and register output + shell: | + echo Username: admin + echo Password: $(kubectl get secret --namespace default nautobot-env -o jsonpath="{.data.NAUTOBOT_SUPERUSER_PASSWORD}" | base64 --decode) + echo api-token: $(kubectl get secret --namespace default nautobot-env -o jsonpath="{.data.NAUTOBOT_SUPERUSER_API_TOKEN}" | base64 --decode) + register: command_output + + - name: Extract variables from command output + set_fact: + nautobot_username: "{{ command_output.stdout_lines[0].split(':')[1].strip() }}" + nautobot_password: "{{ command_output.stdout_lines[1].split(':')[1].strip() }}" + nautobot_api_token: "{{ command_output.stdout_lines[2].split(':')[1].strip() }}" + + - name: Write variables to a YAML file + copy: + content: | + username: "{{ nautobot_username }}" + password: "{{ nautobot_password }}" + api_token: "{{ nautobot_api_token }}" + dest: nautobot_secrets.yml + tags: staging-install + +- block: + - name: Add Nautobot Helm repository + community.kubernetes.helm_repository: + name: nautobot + repo_url: https://nautobot.github.io/helm-charts/ + + - name: Update Helm Nautobot repositories + community.kubernetes.helm_repository: + name: nautobot + repo_url: https://nautobot.github.io/helm-charts/ + force_update: yes + + - name: Install Nautobot Helm chart + community.kubernetes.helm: + name: nautobot + chart_ref: nautobot/nautobot + release_namespace: default + values: "{{ nautobot_values }}" + state: present + register: nautobot_output + + - name: Apply Traefik-Nautobot ingress + community.kubernetes.k8s: + state: present + definition: "{{ lookup('template', 'production-ingress.j2') }}" + register: ingress_output + + - name: Run command and register output + shell: | + echo Username: admin + echo Password: $(kubectl get secret --namespace default nautobot-env -o jsonpath="{.data.NAUTOBOT_SUPERUSER_PASSWORD}" | base64 --decode) + echo api-token: $(kubectl get secret --namespace default nautobot-env -o jsonpath="{.data.NAUTOBOT_SUPERUSER_API_TOKEN}" | base64 --decode) + register: command_output + + - name: Extract variables from command output + set_fact: + nautobot_username: "{{ command_output.stdout_lines[0].split(':')[1].strip() }}" + nautobot_password: "{{ command_output.stdout_lines[1].split(':')[1].strip() }}" + nautobot_api_token: "{{ command_output.stdout_lines[2].split(':')[1].strip() }}" + + - name: Write variables to a YAML file + copy: + content: | + username: "{{ nautobot_username }}" + password: "{{ nautobot_password }}" + api_token: "{{ nautobot_api_token }}" + dest: nautobot_secrets.yml + tags: production-install \ No newline at end of file diff --git a/roles/install-nautobot/templates/production-ingress.j2 b/roles/install-nautobot/templates/production-ingress.j2 new file mode 100644 index 000000000..7a5f17e80 --- /dev/null +++ b/roles/install-nautobot/templates/production-ingress.j2 @@ -0,0 +1,21 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: nautobot + namespace: default + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`nautobot.{{ install_domain }}`) + kind: Rule + services: + - name: nautobot-default + port: 80 + middlewares: + - name: default-headers + tls: + secretName: {{ production_homelab_secret }} diff --git a/roles/nautobot/templates/ingress.j2 b/roles/install-nautobot/templates/staging-ingress.j2 similarity index 79% rename from roles/nautobot/templates/ingress.j2 rename to roles/install-nautobot/templates/staging-ingress.j2 index 429548382..13ab540aa 100644 --- a/roles/nautobot/templates/ingress.j2 +++ b/roles/install-nautobot/templates/staging-ingress.j2 @@ -10,7 +10,7 @@ spec: entryPoints: - websecure routes: - - match: Host(`nautobot.local.byrnbaker.me`) + - match: Host(`nautobot.{{ install_domain }}`) kind: Rule services: - name: nautobot-default @@ -18,4 +18,4 @@ spec: middlewares: - name: default-headers tls: - secretName: local-byrnbaker-me-staging-tls + secretName: {{ staging_secret }} diff --git a/roles/nautobot/vars/main.yml b/roles/install-nautobot/vars/main.yml similarity index 69% rename from roles/nautobot/vars/main.yml rename to roles/install-nautobot/vars/main.yml index 7d7af49c7..dddcf7eab 100644 --- a/roles/nautobot/vars/main.yml +++ b/roles/install-nautobot/vars/main.yml @@ -1,16 +1,21 @@ nautobot_values: nautobot: + autoscale: + enabled: true + minReplicas: 2 + maxReplicas: 4 image: registry: "ghcr.io" - repository: "nautobot/nautobot" - tag: "2.1.5-py3.11" # Latest Version - pullPolicy: "Always" + repository: "byrn-baker/nautobot-kubernetes" + tag: "v0.0.4" + pullSecrets: + - "ghcr.io" replicaCount: 2 metrics: true secretKey: "{{ nb_secretKey }}" superUser: enabled: true - username: "{{ nb_usernam }}" + username: "{{ nb_username }}" password: "{{ nb_password }}" extraVars: - name: "NAUTOBOT_BANNER_TOP" diff --git a/roles/install-prometheus/tasks/main.yml b/roles/install-prometheus/tasks/main.yml new file mode 100644 index 000000000..e71c038fe --- /dev/null +++ b/roles/install-prometheus/tasks/main.yml @@ -0,0 +1,38 @@ +--- +### Installing Prometheus +- block: + # - name: Add Prometheus Helm repository + # kubernetes.core.helm_repository: + # name: prometheus + # repo_url: https://prometheus-community.github.io/helm-charts + + # - name: Update Helm Prometheus repositories + # kubernetes.core.helm_repository: + # name: prometheus + # repo_url: https://prometheus-community.github.io/helm-charts + # force_update: yes + + # - name: Create prometheus namespace + # kubernetes.core.k8s: + # api_version: v1 + # kind: Namespace + # name: prometheus + # state: present + + # - name: Install prometheus-stack Helm chart + # kubernetes.core.helm: + # name: prometheus-stack + # chart_ref: prometheus-community/kube-prometheus-stack + # release_namespace: prometheus + # state: present + + # - name: Apply default headers + # kubernetes.core.k8s: + # state: present + # definition: "{{ lookup('template', 'default-headers.j2') }}" + + - name: Apply Traefik-Nautobot ingress + community.kubernetes.k8s: + state: present + definition: "{{ lookup('template', 'production-ingress.j2') }}" + register: ingress_output \ No newline at end of file diff --git a/roles/install-prometheus/templates/default-headers.j2 b/roles/install-prometheus/templates/default-headers.j2 new file mode 100644 index 000000000..07dfdf714 --- /dev/null +++ b/roles/install-prometheus/templates/default-headers.j2 @@ -0,0 +1,16 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: default-headers + namespace: prometheus +spec: + headers: + browserXssFilter: true + contentTypeNosniff: true + forceSTSHeader: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 15552000 + customFrameOptionsValue: SAMEORIGIN + customRequestHeaders: + X-Forwarded-Proto: https \ No newline at end of file diff --git a/roles/install-prometheus/templates/production-ingress.j2 b/roles/install-prometheus/templates/production-ingress.j2 new file mode 100644 index 000000000..165375bf8 --- /dev/null +++ b/roles/install-prometheus/templates/production-ingress.j2 @@ -0,0 +1,21 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: prometheus + namespace: default + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`grafana.{{ install_domain }}`) + kind: Rule + services: + - name: prometheus-grafana + port: 80 + middlewares: + - name: default-headers + tls: + secretName: {{ production_homelab_secret }} \ No newline at end of file diff --git a/roles/install-rancher-ui/tasks/main.yml b/roles/install-rancher-ui/tasks/main.yml new file mode 100644 index 000000000..5045539ed --- /dev/null +++ b/roles/install-rancher-ui/tasks/main.yml @@ -0,0 +1,86 @@ +--- +- block: + - name: Add Rancher Helm repository + kubernetes.core.helm_repository: + name: rancher + repo_url: https://releases.rancher.com/server-charts/stable + + - name: Update Helm Rancher repositories + kubernetes.core.helm_repository: + name: rancher + repo_url: https://releases.rancher.com/server-charts/stable + force_update: yes + + - name: Check if cert-manager is installed + kubernetes.core.k8s_info: + kind: Namespace + name: cert-manager + register: cert_manager_namespace + + - fail: + msg: "cert-manager is not installed. Please install cert-manager before proceeding." + when: cert_manager_namespace.resources | length == 0 + + - name: Create cattle-system namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: cattle-system + state: present + + - name: Install Rancher Helm chart + kubernetes.core.helm: + name: rancher + chart_ref: rancher-stable/rancher + release_namespace: cattle-system + values: "{{staging_rancher_ui_values }}" + state: present + + - name: Apply Production Traefik ingress for Rancher UI + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'staging-ingress.j2') }}" + tags: staging-install + +- block: + - name: Add Rancher Helm repository + kubernetes.core.helm_repository: + name: rancher + repo_url: https://releases.rancher.com/server-charts/stable + + - name: Update Helm Rancher repositories + kubernetes.core.helm_repository: + name: rancher + repo_url: https://releases.rancher.com/server-charts/stable + force_update: yes + + - name: Check if cert-manager is installed + kubernetes.core.k8s_info: + kind: Namespace + name: cert-manager + register: cert_manager_namespace + + - fail: + msg: "cert-manager is not installed. Please install cert-manager before proceeding." + when: cert_manager_namespace.resources | length == 0 + + - name: Create cattle-system namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: cattle-system + state: present + + - name: Install Rancher Helm chart + kubernetes.core.helm: + name: rancher + chart_ref: rancher-stable/rancher + release_namespace: cattle-system + values: "{{production_rancher_ui_values}}" + state: present + + - name: Apply Production Traefik ingress for Rancher UI + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'production-ingress.j2') }}" + tags: production-install \ No newline at end of file diff --git a/roles/install-rancher-ui/templates/production-ingress.j2 b/roles/install-rancher-ui/templates/production-ingress.j2 new file mode 100644 index 000000000..5c7a5c58c --- /dev/null +++ b/roles/install-rancher-ui/templates/production-ingress.j2 @@ -0,0 +1,18 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: rancher-ui-ingress + namespace: cattle-system + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`rancher.{{ install_domain }}`) + kind: Rule + services: + - name: rancher + port: 443 + tls: + secretName: {{ production_secret }} \ No newline at end of file diff --git a/roles/install-rancher-ui/templates/staging-ingress.j2 b/roles/install-rancher-ui/templates/staging-ingress.j2 new file mode 100644 index 000000000..be27f8703 --- /dev/null +++ b/roles/install-rancher-ui/templates/staging-ingress.j2 @@ -0,0 +1,18 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: rancher-ui-ingress + namespace: cattle-system + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`rancher.{{ install_domain }}`) + kind: Rule + services: + - name: rancher + port: 443 + tls: + secretName: {{ staging_secret }} \ No newline at end of file diff --git a/roles/install-rancher-ui/vars/main.yml b/roles/install-rancher-ui/vars/main.yml new file mode 100644 index 000000000..3cdb5d93b --- /dev/null +++ b/roles/install-rancher-ui/vars/main.yml @@ -0,0 +1,17 @@ +staging_rancher_ui_values: + hostname: "{{ rancher_hostname}}" + replicas: 3 + bootstrapPassword: "{{ rancher_password }}" + version: "{{ rancher_version }}" + ingress: + tls: + source: local-byrnbaker-me-staging-tls + +production_rancher_ui_values: + hostname: "{{ rancher_hostname}}" + replicas: 3 + bootstrapPassword: "{{ rancher_password }}" + version: "{{ rancher_version }}" + ingress: + tls: + source: local-byrnbaker-me-tls \ No newline at end of file diff --git a/roles/install-traefik/tasks/main.yml b/roles/install-traefik/tasks/main.yml new file mode 100644 index 000000000..950bc7268 --- /dev/null +++ b/roles/install-traefik/tasks/main.yml @@ -0,0 +1,133 @@ +--- +- block: + - name: Add Traefik Helm repository + kubernetes.core.helm_repository: + name: traefik + repo_url: https://helm.traefik.io/traefik + + - name: Update Helm repositories + kubernetes.core.helm_repository: + name: traefik + repo_url: https://helm.traefik.io/traefik + force_update: yes + + - name: Create Traefik namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: traefik + state: present + + - name: Install Traefik Helm chart + kubernetes.core.helm: + name: traefik + chart_ref: traefik/traefik + release_namespace: kube-system + values: "{{ traefik_values }}" + state: present + + - name: Apply default headers + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'default-headers.j2') }}" + + - name: Get middleware + kubernetes.core.k8s_info: + kind: Middleware + namespace: default + register: middleware + + - name: Generate base64-encoded admin password + shell: htpasswd -nb admin password | openssl base64 + register: base64_password + + - debug: + var: base64_password.stdout + + - name: Apply Traefik dashboard secret + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'secret-dashboard.j2') }}" + + - name: Get secrets in Traefik namespace + kubernetes.core.k8s_info: + kind: Secret + namespace: default + register: secrets + + - name: Apply middleware + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'middleware.j2') }}" + + - name: Apply Traefik ingress + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'staging-ingress.j2') }}" + tags: staging-install + +- block: + - name: Add Traefik Helm repository + kubernetes.core.helm_repository: + name: traefik + repo_url: https://helm.traefik.io/traefik + + - name: Update Helm repositories + kubernetes.core.helm_repository: + name: traefik + repo_url: https://helm.traefik.io/traefik + force_update: yes + + - name: Create Traefik namespace + kubernetes.core.k8s: + api_version: v1 + kind: Namespace + name: traefik + state: present + + - name: Install Traefik Helm chart + kubernetes.core.helm: + name: traefik + chart_ref: traefik/traefik + release_namespace: kube-system + values: "{{ traefik_values }}" + state: present + + - name: Apply default headers + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'default-headers.j2') }}" + + - name: Get middleware + kubernetes.core.k8s_info: + kind: Middleware + namespace: default + register: middleware + + - name: Generate base64-encoded admin password + shell: htpasswd -nb admin password | openssl base64 + register: base64_password + + - debug: + var: base64_password.stdout + + - name: Apply Traefik dashboard secret + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'secret-dashboard.j2') }}" + + - name: Get secrets in Traefik namespace + kubernetes.core.k8s_info: + kind: Secret + namespace: default + register: secrets + + - name: Apply middleware + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'middleware.j2') }}" + - name: Apply Production Traefik ingress + kubernetes.core.k8s: + state: present + definition: "{{ lookup('template', 'production-ingress.j2') }}" + tags: production-install \ No newline at end of file diff --git a/roles/traefik/templates/default-headers.j2 b/roles/install-traefik/templates/default-headers.j2 similarity index 100% rename from roles/traefik/templates/default-headers.j2 rename to roles/install-traefik/templates/default-headers.j2 diff --git a/roles/traefik/templates/middleware.j2 b/roles/install-traefik/templates/middleware.j2 similarity index 100% rename from roles/traefik/templates/middleware.j2 rename to roles/install-traefik/templates/middleware.j2 diff --git a/roles/traefik/templates/ingress.j2 b/roles/install-traefik/templates/production-ingress.j2 similarity index 76% rename from roles/traefik/templates/ingress.j2 rename to roles/install-traefik/templates/production-ingress.j2 index 74c408777..77c62921c 100644 --- a/roles/traefik/templates/ingress.j2 +++ b/roles/install-traefik/templates/production-ingress.j2 @@ -1,21 +1,21 @@ -apiVersion: traefik.containo.us/v1alpha1 -kind: IngressRoute -metadata: - name: traefik-dashboard - namespace: traefik - annotations: - kubernetes.io/ingress.class: traefik-external -spec: - entryPoints: - - websecure - routes: - - match: Host(`traefik.local.byrnbaker.me`) - kind: Rule - middlewares: - - name: traefik-dashboard-basicauth - namespace: traefik - services: - - name: api@internal - kind: TraefikService -# tls: -# secretName: local-example-com-staging-tls +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: traefik-dashboard + namespace: traefik + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`traefik.{{ install_domain }}`) + kind: Rule + middlewares: + - name: traefik-dashboard-basicauth + namespace: traefik + services: + - name: api@internal + kind: TraefikService + tls: + secretName: {{ production_secret }} diff --git a/roles/traefik/templates/secret-dashboard.j2 b/roles/install-traefik/templates/secret-dashboard.j2 similarity index 100% rename from roles/traefik/templates/secret-dashboard.j2 rename to roles/install-traefik/templates/secret-dashboard.j2 diff --git a/roles/install-traefik/templates/staging-ingress.j2 b/roles/install-traefik/templates/staging-ingress.j2 new file mode 100644 index 000000000..000897155 --- /dev/null +++ b/roles/install-traefik/templates/staging-ingress.j2 @@ -0,0 +1,21 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: traefik-dashboard + namespace: traefik + annotations: + kubernetes.io/ingress.class: traefik-external +spec: + entryPoints: + - websecure + routes: + - match: Host(`traefik.{{ install_domain }}`) + kind: Rule + middlewares: + - name: traefik-dashboard-basicauth + namespace: traefik + services: + - name: api@internal + kind: TraefikService + tls: + secretName: {{ staging_secret }} diff --git a/roles/traefik/vars/main.yml b/roles/install-traefik/vars/main.yml similarity index 85% rename from roles/traefik/vars/main.yml rename to roles/install-traefik/vars/main.yml index 74b6c8d18..61e865d21 100644 --- a/roles/traefik/vars/main.yml +++ b/roles/install-traefik/vars/main.yml @@ -23,7 +23,11 @@ traefik_values: websecure: tls: enabled: true - + gitlab-ssh: + port: 22 + expose: true + exposedPort: 22 + protocol: TCP ingressRoute: dashboard: enabled: false @@ -48,6 +52,6 @@ traefik_values: annotations: {} labels: {} spec: - loadBalancerIP: 192.168.104.80 # this should be an IP in the MetalLB range + loadBalancerIP: 192.168.30.80 # this should be an IP in the MetalLB range loadBalancerSourceRanges: [] externalIPs: [] diff --git a/roles/k3s_server_post/tasks/metallb.yml b/roles/k3s_server_post/tasks/metallb.yml index 07a23b0f2..36b624850 100644 --- a/roles/k3s_server_post/tasks/metallb.yml +++ b/roles/k3s_server_post/tasks/metallb.yml @@ -45,6 +45,10 @@ with_items: "{{ groups[group_name_master | default('master')] }}" run_once: true +- name: Wait for 1 minute + pause: + minutes: 1 + - name: Wait for MetalLB resources command: >- k3s kubectl wait {{ item.resource }} @@ -83,6 +87,7 @@ loop_control: label: "{{ item.description }}" + - name: Test metallb-system webhook-service endpoint command: >- k3s kubectl -n metallb-system get endpoints webhook-service diff --git a/roles/nautobot/tasks/main.yml b/roles/nautobot/tasks/main.yml deleted file mode 100644 index 152289e09..000000000 --- a/roles/nautobot/tasks/main.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -- name: Add Nautobot Helm repository - community.kubernetes.helm_repository: - name: nautobot - repo_url: https://nautobot.github.io/helm-charts/ - -- name: Update Helm Nautobot repositories - community.kubernetes.helm_repository: - name: nautobot - repo_url: https://nautobot.github.io/helm-charts/ - force_update: yes - -- name: Install Nautobot Helm chart - community.kubernetes.helm: - name: nautobot - chart_ref: nautobot/nautobot - release_namespace: default - values: "{{ nautobot_values }}" - state: present - register: nautobot_output - -- debug: - var: nautobot_output.stdout - -- name: Get Nautobot pods - kubernetes.core.k8s_info: - api_version: v1 - kind: Pod - label_selectors: - - app.kubernetes.io/name=nautobot - register: nautobot_pods - -- debug: - var: nautobot_pods - -- name: Apply Traefik-Nautobot ingress - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'ingress.j2') }}" - register: ingress_output - -- debug: - var: ingress_output.stdout - -- name: Run command and register output - shell: | - echo Username: admin - echo Password: $(kubectl get secret --namespace default nautobot-env -o jsonpath="{.data.NAUTOBOT_SUPERUSER_PASSWORD}" | base64 --decode) - echo api-token: $(kubectl get secret --namespace default nautobot-env -o jsonpath="{.data.NAUTOBOT_SUPERUSER_API_TOKEN}" | base64 --decode) - register: command_output - -- name: Extract variables from command output - set_fact: - nautobot_username: "{{ command_output.stdout_lines[0].split(':')[1].strip() }}" - nautobot_password: "{{ command_output.stdout_lines[1].split(':')[1].strip() }}" - nautobot_api_token: "{{ command_output.stdout_lines[2].split(':')[1].strip() }}" - -- name: Write variables to a YAML file - copy: - content: | - username: "{{ nautobot_username }}" - password: "{{ nautobot_password }}" - api_token: "{{ nautobot_api_token }}" - dest: nautobot_secrets.yml \ No newline at end of file diff --git a/roles/proxmox_vm/tasks/main.yml b/roles/proxmox_vm/tasks/main.yml deleted file mode 100644 index 7600f10d9..000000000 --- a/roles/proxmox_vm/tasks/main.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- name: Include tasks for each host in k3s_cluster - include_tasks: build_vms.yml - loop: "{{ groups['k3s_cluster'] }}" - loop_control: - loop_var: target_host - vars: - host_ip: "{{ hostvars[target_host]['ansible_host'] }}" \ No newline at end of file diff --git a/roles/traefik/tasks/main.yml b/roles/traefik/tasks/main.yml deleted file mode 100644 index 592780411..000000000 --- a/roles/traefik/tasks/main.yml +++ /dev/null @@ -1,82 +0,0 @@ ---- -- name: Add Traefik Helm repository - community.kubernetes.helm_repository: - name: traefik - repo_url: https://helm.traefik.io/traefik - -- name: Update Helm repositories - community.kubernetes.helm_repository: - name: traefik - repo_url: https://helm.traefik.io/traefik - force_update: yes - -- name: Create Traefik namespace - community.kubernetes.k8s: - api_version: v1 - kind: Namespace - name: traefik - state: present - -- name: Get all namespaces - community.kubernetes.k8s_info: - kind: Namespace - register: namespaces - -- name: Install Traefik Helm chart - community.kubernetes.helm: - name: traefik - chart_ref: traefik/traefik - release_namespace: traefik - values: "{{ traefik_values }}" - state: present - -- name: Get all services - community.kubernetes.k8s_info: - kind: Service - namespace: traefik - register: services - -- name: Get Traefik pods - community.kubernetes.k8s_info: - kind: Pod - namespace: traefik - register: pods - -- name: Apply default headers - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'default-headers.j2') }}" - -- name: Get middleware - community.kubernetes.k8s_info: - kind: Middleware - namespace: traefik - register: middleware - -- name: Generate base64-encoded admin password - shell: htpasswd -nb admin password | openssl base64 - register: base64_password - -- debug: - var: base64_password.stdout - -- name: Apply Traefik dashboard secret - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'secret-dashboard.j2') }}" - -- name: Get secrets in Traefik namespace - community.kubernetes.k8s_info: - kind: Secret - namespace: traefik - register: secrets - -- name: Apply middleware - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'middleware.j2') }}" - -- name: Apply Traefik ingress - community.kubernetes.k8s: - state: present - definition: "{{ lookup('template', 'ingress.j2') }}" \ No newline at end of file diff --git a/site.yml b/site.yml index 4b0c1049a..70324b2ca 100644 --- a/site.yml +++ b/site.yml @@ -1,6 +1,6 @@ --- - name: Pre tasks - hosts: all + hosts: k3s_cluster pre_tasks: - name: Verify Ansible is version 2.11 or above. (If this fails you may need to update Ansible) assert: @@ -66,3 +66,18 @@ dest: ./kubeconfig flat: true when: ansible_hostname == hostvars[groups[group_name_master | default('master')][0]]['ansible_hostname'] + +- name: Copy kueconfig into .kubeconfig/config + hosts: localhost + environment: "{{ proxy_env | default({}) }}" + tasks: + - name: Create .kube directory + ansible.builtin.file: + path: "{{ ansible_user_dir }}/.kube" + state: directory + + - name: Copy kubeconfig into .kube/config + ansible.builtin.copy: + src: ./kubeconfig + dest: "{{ home_dir }}/.kube/config" + mode: '0600' \ No newline at end of file diff --git a/switch_deployment_to_production.sh b/switch_deployment_to_production.sh new file mode 100755 index 000000000..9c8c7d98f --- /dev/null +++ b/switch_deployment_to_production.sh @@ -0,0 +1,5 @@ +#!/bin/bash +ansible-playbook install-cert-manager.yml --tags "production-install" +ansible-playbook install-traefik.yml --tags "production-install" +ansible-playbook install-rancher-ui.yml --tags "production-install" +ansible-playbook install-gitlab.yml --tags "production-install" \ No newline at end of file