feat!: Rewrite configure script in ansible (#822)
* feat: smol changes Signed-off-by: Devin Buhl <devin@buhl.casa> * feat!: Rewrite configure script in ansible Signed-off-by: Devin Buhl <devin@buhl.casa> * fix: add comment for real path Signed-off-by: Devin Buhl <devin@buhl.casa> * feat: verification is done Signed-off-by: Devin Buhl <devin@buhl.casa> * fix: do not need net common Signed-off-by: Devin Buhl <devin@buhl.casa> * chore: clean up old comments Signed-off-by: Devin Buhl <devin@buhl.casa> * chore: clean up old comments Signed-off-by: Devin Buhl <devin@buhl.casa> * feat: finish up ansible host generation and more Signed-off-by: Devin Buhl <devin@buhl.casa> * fix: backup/restore sops config between tasks Signed-off-by: Devin Buhl <devin@buhl.casa> * chore: clean up test comments Signed-off-by: Devin Buhl <devin@buhl.casa> * fix: use ansible template on template Signed-off-by: Devin Buhl <devin@buhl.casa> * Update template/vars/config.sample.yaml Co-authored-by: Manuel Rüger <manuel@rueg.eu> * Update template/tasks/verify_cli.yaml * chore: update readme Signed-off-by: Devin Buhl <devin@buhl.casa> * chore: update readme Signed-off-by: Devin Buhl <devin@buhl.casa> * Update README.md Co-authored-by: Manuel Rüger <manuel@rueg.eu> * fix: move bcrypt logic to template Signed-off-by: Devin Buhl <devin@buhl.casa> * fix: derp Signed-off-by: Devin Buhl <devin@buhl.casa> --------- Signed-off-by: Devin Buhl <devin@buhl.casa> Co-authored-by: Manuel Rüger <manuel@rueg.eu>
This commit is contained in:
1 parent
26164c7278
commit
de0ed1c70a
58 files changed
+698
-764
No files matched your search
@@ -1,99 +0,0 @@
|
||||
#
|
||||
# Cluster related variables
|
||||
#
|
||||
|
||||
# The repo you created from this template
|
||||
# e.g. https://github.com/onedr0p/home-cluster
|
||||
export BOOTSTRAP_GIT_REPOSITORY=""
|
||||
|
||||
# To enable Flux to update your cluster on `git push` set the following to one of:
|
||||
# `generated` - this will generate a token and print it in the logs
|
||||
# Set this to any other string and it will be used for the secret
|
||||
export BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET="generated" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
|
||||
# The Weave GitOps dashboard admin password
|
||||
# `generated` - this will generate a token and print it in the logs
|
||||
# Set this to any other string and it will be used for the secret
|
||||
export BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD="generated" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
|
||||
# The Grafana admin password
|
||||
# `generated` - this will generate a token and print it in the logs
|
||||
# Set this to any other string and it will be used for the secret
|
||||
export BOOTSTRAP_GRAFANA_ADMIN_PASSWORD="generated" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
|
||||
# Age Public Key - string should start with age
|
||||
# e.g. age15uzrw396e67z9wdzsxzdk7ka0g2gr3l460e0slaea563zll3hdfqwqxdta
|
||||
export BOOTSTRAP_AGE_PUBLIC_KEY=""
|
||||
|
||||
# Choose your timezone
|
||||
# e.g. America/New_York
|
||||
export BOOTSTRAP_TIMEZONE="Etc/UTC"
|
||||
|
||||
# The email you want to be associated with the ACME account
|
||||
export BOOTSTRAP_ACME_EMAIL=""
|
||||
|
||||
# Choose one of your cloudflare domains
|
||||
# e.g. onedr0p.com
|
||||
export BOOTSTRAP_CLOUDFLARE_DOMAIN=""
|
||||
# Your global Cloudflare API Token (not API Key)
|
||||
export BOOTSTRAP_CLOUDFLARE_APITOKEN=""
|
||||
|
||||
# Create a cloudflare tunnel to automatically ingress into your cluster
|
||||
# https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/local/
|
||||
# cloudflared tunnel create <NAME or UUID>
|
||||
export BOOTSTRAP_CLOUDFLARE_ACCOUNT_TAG=""
|
||||
export BOOTSTRAP_CLOUDFLARE_TUNNEL_SECRET=""
|
||||
export BOOTSTRAP_CLOUDFLARE_TUNNEL_ID=""
|
||||
|
||||
# The load balancer IP for k8s_gateway, choose a available IP in your nodes network that is not being used
|
||||
export BOOTSTRAP_K8S_GATEWAY_ADDR=""
|
||||
# The load balancer IP for ingress-nginx, choose a available IP in your nodes network that is not being used
|
||||
export BOOTSTRAP_INGRESS_NGINX_ADDR=""
|
||||
# The IP address to use with kube-vip, choose a available IP in your nodes network that is not being used
|
||||
export BOOTSTRAP_KUBE_VIP_ADDR=""
|
||||
# Choose your cluster or service cidrs
|
||||
# Leave this unchanged unless you know what you are doing
|
||||
export BOOTSTRAP_CLUSTER_CIDR="10.42.0.0/16"
|
||||
export BOOTSTRAP_SERVICE_CIDR="10.43.0.0/16"
|
||||
|
||||
#
|
||||
# Ansible related variables
|
||||
#
|
||||
|
||||
#
|
||||
# Default prefixes for hostnames assigned by Ansible
|
||||
# These are unused on nodes where BOOTSTRAP_ANSIBLE_HOSTNAME_ is provided
|
||||
#
|
||||
|
||||
export BOOTSTRAP_ANSIBLE_DEFAULT_CONTROL_NODE_HOSTNAME_PREFIX="k8s-" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
export BOOTSTRAP_ANSIBLE_DEFAULT_NODE_HOSTNAME_PREFIX="k8s-" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
|
||||
#
|
||||
# Ansible hosts - repeat this block as many times as you need,
|
||||
# incrementing the last digit on the variable name for each node
|
||||
#
|
||||
|
||||
# IP Address to the node
|
||||
# That doesn't conflict with any other IP addresses here
|
||||
# e.g. 192.168.1.200
|
||||
export BOOTSTRAP_ANSIBLE_HOST_ADDR_0=""
|
||||
# User Ansible will log into the nodes
|
||||
export BOOTSTRAP_ANSIBLE_SSH_USERNAME_0="" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
# Password Ansible will use to escalate to sudo
|
||||
export BOOTSTRAP_ANSIBLE_SUDO_PASSWORD_0="" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
# Set this node as a control node (true/false)
|
||||
export BOOTSTRAP_ANSIBLE_CONTROL_NODE_0=""
|
||||
# Optional: Set the hostname of the node, if set this will override the *_HOSTNAME_PREFIX vars above
|
||||
export BOOTSTRAP_ANSIBLE_HOSTNAME_0=""
|
||||
|
||||
# export BOOTSTRAP_ANSIBLE_HOST_ADDR_1=""
|
||||
# export BOOTSTRAP_ANSIBLE_SSH_USERNAME_1="" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
# export BOOTSTRAP_ANSIBLE_SUDO_PASSWORD_1="" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
# export BOOTSTRAP_ANSIBLE_CONTROL_NODE_1=""
|
||||
# export BOOTSTRAP_ANSIBLE_HOSTNAME_1=""
|
||||
|
||||
# export BOOTSTRAP_ANSIBLE_HOST_ADDR_2=""
|
||||
# export BOOTSTRAP_ANSIBLE_SSH_USERNAME_2="" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
# export BOOTSTRAP_ANSIBLE_SUDO_PASSWORD_2="" # NOTE: Must only contain alphanumeric characters and dashes
|
||||
# export BOOTSTRAP_ANSIBLE_CONTROL_NODE_2=""
|
||||
# export BOOTSTRAP_ANSIBLE_HOSTNAME_2=""
|
||||
@@ -1,6 +1,4 @@
|
||||
#shellcheck disable=SC2148,SC2155
|
||||
export KUBECONFIG=$(expand_path ./kubeconfig)
|
||||
export ANSIBLE_CONFIG=$(expand_path ./ansible.cfg)
|
||||
export ANSIBLE_HOST_KEY_CHECKING="False"
|
||||
export K8S_AUTH_KUBECONFIG=$(expand_path ./kubeconfig)
|
||||
export SOPS_AGE_KEY_FILE=$(expand_path ~/.config/sops/age/keys.txt)
|
||||
@@ -1,5 +1,4 @@
|
||||
* text=auto eol=lf
|
||||
*.yml.j2 linguist-language=YAML
|
||||
*.yaml.j2 linguist-language=YAML
|
||||
*.sops.* diff=sopsdiffer
|
||||
*.sops.toml linguist-language=JSON
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
version: "3"
|
||||
|
||||
env:
|
||||
K8S_AUTH_KUBECONFIG: "{{.ROOT_DIR}}/kubeconfig"
|
||||
|
||||
vars:
|
||||
ANSIBLE_PLAYBOOK_DIR: "{{.ANSIBLE_DIR}}/playbooks"
|
||||
ANSIBLE_INVENTORY_DIR: "{{.ANSIBLE_DIR}}/inventory"
|
||||
|
||||
tasks:
|
||||
|
||||
init:
|
||||
desc: Install / Upgrade Ansible galaxy deps
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- pip3 install --user --requirement requirements.txt
|
||||
- ansible-galaxy install -r requirements.yaml --roles-path ~/.ansible/roles --force
|
||||
- ansible-galaxy collection install -r requirements.yaml --collections-path ~/.ansible/collections --force
|
||||
|
||||
list:
|
||||
desc: List all the hosts
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml --list-hosts
|
||||
|
||||
prepare:
|
||||
desc: Prepare all the k8s nodes for running k3s
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-prepare.yaml
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
|
||||
install:
|
||||
desc: Install Kubernetes on the nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-installation.yaml
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
|
||||
upgrade:
|
||||
desc: Upgrade Kubernetes on the nodes (currently this only upgrades kube-vip)
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-upgrade.yaml
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
|
||||
nuke:
|
||||
desc: Uninstall Kubernetes on the nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
interactive: true
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-nuke.yaml
|
||||
- task: force-reboot
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
|
||||
ping:
|
||||
desc: Ping all the hosts
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml --one-line -m 'ping'
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
|
||||
uptime:
|
||||
desc: Uptime of all the hosts
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml --one-line -a 'uptime'
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
|
||||
force-reboot:
|
||||
desc: Reboot all the k8s nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-reboot.yaml
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
|
||||
force-poweroff:
|
||||
desc: Shutdown all the k8s nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible kubernetes -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml -a '/usr/bin/systemctl poweroff' --become
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg"
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
version: "3"
|
||||
|
||||
env:
|
||||
ANSIBLE_CONFIG: "{{.ROOT_DIR}}/ansible.cfg"
|
||||
K8S_AUTH_KUBECONFIG: "{{.ROOT_DIR}}/kubeconfig"
|
||||
|
||||
vars:
|
||||
ANSIBLE_PLAYBOOK_DIR: "{{.ANSIBLE_DIR}}/playbooks"
|
||||
ANSIBLE_INVENTORY_DIR: "{{.ANSIBLE_DIR}}/inventory"
|
||||
|
||||
tasks:
|
||||
|
||||
init:
|
||||
desc: Install / Upgrade Ansible galaxy deps
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- pip3 install --user --requirement requirements.txt
|
||||
- ansible-galaxy install -r requirements.yml --roles-path ~/.ansible/roles --force
|
||||
- ansible-galaxy collection install -r requirements.yml --collections-path ~/.ansible/collections --force
|
||||
|
||||
list:
|
||||
desc: List all the hosts
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml --list-hosts
|
||||
|
||||
prepare:
|
||||
desc: Prepare all the k8s nodes for running k3s
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-prepare.yml
|
||||
|
||||
install:
|
||||
desc: Install Kubernetes on the nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-installation.yml
|
||||
|
||||
upgrade:
|
||||
desc: Upgrade Kubernetes on the nodes (currently this only upgrades kube-vip)
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-upgrade.yml
|
||||
|
||||
nuke:
|
||||
desc: Uninstall Kubernetes on the nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
interactive: true
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-nuke.yml
|
||||
- task: force-reboot
|
||||
|
||||
ping:
|
||||
desc: Ping all the hosts
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml --one-line -m 'ping'
|
||||
|
||||
uptime:
|
||||
desc: Uptime of all the hosts
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml --one-line -a 'uptime'
|
||||
|
||||
force-reboot:
|
||||
desc: Reboot all the k8s nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-reboot.yml
|
||||
|
||||
force-poweroff:
|
||||
desc: Shutdown all the k8s nodes
|
||||
dir: "{{.ANSIBLE_DIR}}"
|
||||
cmds:
|
||||
- ansible kubernetes -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yml -a '/usr/bin/systemctl poweroff' --become
|
||||
File renamed without changes.
Vendored
+4
-4
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.json5": "jsonc",
|
||||
"**/ansible/**/*.yml": "ansible",
|
||||
"**/ansible/**/*.sops.yml": "yaml",
|
||||
"**/ansible/**/inventory/**/*.yml": "yaml",
|
||||
"**/ansible/**/*.yaml": "ansible",
|
||||
"**/ansible/**/*.sops.yaml": "yaml",
|
||||
"**/ansible/**/inventory/**/*.yaml": "yaml",
|
||||
"**/kubernetes/**/*.sops.toml": "plaintext"
|
||||
},
|
||||
"yaml.schemas": {
|
||||
"ansible": "ansible/*.yml",
|
||||
"ansible": "ansible/*.yaml",
|
||||
"Kubernetes": "kubernetes/*.yaml"
|
||||
},
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
|
||||
@@ -79,9 +79,9 @@ Clone **your new repo** to you local workstation and `cd` into it.
|
||||
|
||||
1. Install the following CLI tools on your workstation, if you are **NOT** using [Homebrew](https://brew.sh/) on MacOS or Linux **ignore** steps 4 and 5.
|
||||
|
||||
- Required: [age](https://github.com/FiloSottile/age), [ansible](https://www.ansible.com), [flux](https://toolkit.fluxcd.io/), [weave-gitops](https://docs.gitops.weave.works/docs/installation/weave-gitops/), [cloudflared](https://github.com/cloudflare/cloudflared), [cilium-cli](https://github.com/cilium/cilium-cli), [go-task](https://github.com/go-task/task), [direnv](https://github.com/direnv/direnv), [ipcalc](http://jodies.de/ipcalc), [jq](https://stedolan.github.io/jq/), [kubectl](https://kubernetes.io/docs/tasks/tools/), [python-pip3](https://pypi.org/project/pip/), [sops v3](https://github.com/mozilla/sops), [yq v4](https://github.com/mikefarah/yq)
|
||||
- Required: [age](https://github.com/FiloSottile/age), [ansible](https://www.ansible.com), [flux](https://toolkit.fluxcd.io/), [cloudflared](https://github.com/cloudflare/cloudflared), [go-task](https://github.com/go-task/task), [direnv](https://github.com/direnv/direnv), [jq](https://stedolan.github.io/jq/), [kubectl](https://kubernetes.io/docs/tasks/tools/), [python-pip3](https://pypi.org/project/pip/), [sops v3](https://github.com/getsops/sops)
|
||||
|
||||
- Recommended: [helm](https://helm.sh/), [kustomize](https://github.com/kubernetes-sigs/kustomize), [stern](https://github.com/stern/stern), [yamllint](https://github.com/adrienverge/yamllint)
|
||||
- Recommended: [helm](https://helm.sh/), [kustomize](https://github.com/kubernetes-sigs/kustomize), [stern](https://github.com/stern/stern)
|
||||
|
||||
2. This guide heavily relies on [go-task](https://github.com/go-task/task) as a framework for setting things up. It is advised to learn and understand the commands it is running under the hood.
|
||||
|
||||
@@ -170,30 +170,22 @@ In order to expose services to the internet you will need to create a [Cloudflar
|
||||
|
||||
### 📄 Configuration
|
||||
|
||||
📍 The `.config.env` file contains necessary configuration that is needed by Ansible and Flux.
|
||||
📍 The `template/vars/config.yaml` file contains necessary configuration that is needed by Ansible and Flux.
|
||||
|
||||
1. Copy the `.config.sample.env` to `.config.env` and start filling out all the environment variables.
|
||||
1. Copy the configuration file and start filling out all the variables.
|
||||
|
||||
**All are required** unless otherwise noted in the comments.
|
||||
|
||||
```sh
|
||||
cp .config.sample.env .config.env
|
||||
cp template/vars/config.sample.yaml template/vars/config.yaml
|
||||
```
|
||||
|
||||
2. Once that is done, verify the configuration is correct by running:
|
||||
|
||||
```sh
|
||||
task verify
|
||||
```
|
||||
|
||||
3. If you do not encounter any errors run start having the script wire up the templated files and place them where they need to be.
|
||||
2. Once done run the following command which will verify and generate the files needed to continue
|
||||
|
||||
```sh
|
||||
task configure
|
||||
```
|
||||
|
||||
⚠️ This will print out the clear-text passwords for Grafana and Weave Gitops if you had them set to `generated` in your `.config.env`. Take note of these, you'll need them to log into the applications.
|
||||
|
||||
### ⚡ Preparing Ubuntu Server with Ansible
|
||||
|
||||
📍 Here we will be running an Ansible Playbook to prepare Ubuntu server for running a Kubernetes cluster.
|
||||
@@ -276,14 +268,14 @@ In order to expose services to the internet you will need to create a [Cloudflar
|
||||
```sh
|
||||
task cluster:verify
|
||||
# ► checking prerequisites
|
||||
# ✔ kubectl 1.21.5 >=1.18.0-0
|
||||
# ✔ Kubernetes 1.21.5+k3s1 >=1.16.0-0
|
||||
# ✔ kubectl 1.27.3 >=1.18.0-0
|
||||
# ✔ Kubernetes 1.27.3+k3s1 >=1.16.0-0
|
||||
# ✔ prerequisites checks passed
|
||||
```
|
||||
|
||||
2. Push you changes to git
|
||||
|
||||
📍 **Verify** all the `*.sops.yaml` and `*.sops.yml` files under the `./ansible`, and `./kubernetes` directories are **encrypted** with SOPS
|
||||
📍 **Verify** all the `*.sops.yaml` and `*.sops.yaml` files under the `./ansible`, and `./kubernetes` directories are **encrypted** with SOPS
|
||||
|
||||
```sh
|
||||
git add -A
|
||||
@@ -456,7 +448,7 @@ To enable Renovate on your repository, click the 'Configure' button over at thei
|
||||
|
||||
Flux is pull-based by design meaning it will periodically check your git repository for changes, using a webhook you can enable Flux to update your cluster on `git push`. In order to configure Github to send `push` events from your repository to the Flux webhook receiver you will need two things:
|
||||
|
||||
1. Webhook URL - Your webhook receiver will be deployed on `https://flux-webhook.${BOOTSTRAP_CLOUDFLARE_DOMAIN}/hook/:hookId`. In order to find out your hook id you can run the following command:
|
||||
1. Webhook URL - Your webhook receiver will be deployed on `https://flux-webhook.${bootstrap_cloudflare_domain}/hook/:hookId`. In order to find out your hook id you can run the following command:
|
||||
|
||||
```sh
|
||||
kubectl -n flux-system get receiver/github-receiver
|
||||
@@ -476,7 +468,7 @@ Flux is pull-based by design meaning it will periodically check your git reposit
|
||||
sops -d ./kubernetes/apps/flux-system/addons/webhooks/github/secret.sops.yaml | yq .stringData.token
|
||||
```
|
||||
|
||||
**Note:** Don't forget to update the `BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET` variable in your `.config.env` file so it matches the generated secret if applicable
|
||||
**Note:** Don't forget to update the `bootstrap_flux_github_webhook_token` variable in the `config.yaml` file so it matches the generated secret if applicable
|
||||
|
||||
Now that you have the webhook url and secret, it's time to set everything up on the Github repository side. Navigate to the settings of your repository on Github, under "Settings/Webhooks" press the "Add webhook" button. Fill in the webhook url and your secret.
|
||||
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
KUBERNETES_DIR: "{{.ROOT_DIR}}/kubernetes"
|
||||
TEMPLATE_DIR: "{{.ROOT_DIR}}/template"
|
||||
ANSIBLE_DIR: "{{.ROOT_DIR}}/ansible"
|
||||
|
||||
dotenv: [".config.env"]
|
||||
KUBERNETES_DIR: "{{.ROOT_DIR}}/kubernetes"
|
||||
|
||||
env:
|
||||
KUBECONFIG: "{{.ROOT_DIR}}/kubeconfig"
|
||||
SOPS_AGE_KEY_FILE: ~/.config/sops/age/keys.txt
|
||||
|
||||
includes:
|
||||
ansible: .taskfiles/AnsibleTasks.yml
|
||||
cluster: .taskfiles/ClusterTasks.yml
|
||||
ansible: .taskfiles/AnsibleTasks.yaml
|
||||
cluster: .taskfiles/ClusterTasks.yaml
|
||||
|
||||
tasks:
|
||||
init:
|
||||
@@ -35,21 +34,14 @@ tasks:
|
||||
fluxcd/tap/flux
|
||||
go-task/tap/go-task
|
||||
helm
|
||||
ipcalc
|
||||
jq
|
||||
kubernetes-cli
|
||||
kustomize
|
||||
sops
|
||||
stern
|
||||
weaveworks/tap/gitops
|
||||
yq
|
||||
|
||||
verify:
|
||||
desc: Verify env settings
|
||||
cmds:
|
||||
- ./configure --verify
|
||||
|
||||
configure:
|
||||
desc: Configure repository from env settings
|
||||
desc: Configure repository from Ansible vars
|
||||
dir: "{{.TEMPLATE_DIR}}"
|
||||
cmds:
|
||||
- ./configure
|
||||
- ansible-playbook configure.yaml
|
||||
@@ -6,10 +6,10 @@ stdout_callback = yaml
|
||||
force_valid_group_names = ignore
|
||||
# File/Directory settings
|
||||
log_path = ~/.ansible/ansible.log
|
||||
inventory = ./ansible/inventory
|
||||
roles_path = ~/.ansible/roles:./ansible/roles
|
||||
inventory = ./inventory
|
||||
roles_path = ~/.ansible/roles:./roles
|
||||
collections_path = ~/.ansible/collections
|
||||
remote_tmp = /tmp
|
||||
remote_tmp = ~/.ansible/tmp
|
||||
local_tmp = ~/.ansible/tmp
|
||||
# Fact Caching settings
|
||||
fact_caching = jsonfile
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+5
-5
@@ -29,7 +29,7 @@
|
||||
k3s_state: installed
|
||||
|
||||
- name: Kubeconfig
|
||||
ansible.builtin.include_tasks: tasks/kubeconfig.yml
|
||||
ansible.builtin.include_tasks: tasks/kubeconfig.yaml
|
||||
|
||||
- name: Wait for custom manifests to rollout
|
||||
when:
|
||||
@@ -58,20 +58,20 @@
|
||||
- k3s_primary_control_node
|
||||
- (k3s_server_manifests_templates | length > 0
|
||||
or k3s_server_manifests_urls | length > 0)
|
||||
ansible.builtin.include_tasks: tasks/coredns.yml
|
||||
ansible.builtin.include_tasks: tasks/coredns.yaml
|
||||
|
||||
- name: Cilium
|
||||
when:
|
||||
- k3s_primary_control_node
|
||||
- (k3s_server_manifests_templates | length > 0
|
||||
or k3s_server_manifests_urls | length > 0)
|
||||
ansible.builtin.include_tasks: tasks/cilium.yml
|
||||
ansible.builtin.include_tasks: tasks/cilium.yaml
|
||||
|
||||
- name: Cruft
|
||||
when: k3s_primary_control_node
|
||||
ansible.builtin.include_tasks: tasks/cruft.yml
|
||||
ansible.builtin.include_tasks: tasks/cruft.yaml
|
||||
|
||||
- name: Stale Containers
|
||||
ansible.builtin.include_tasks: tasks/stale_containers.yml
|
||||
ansible.builtin.include_tasks: tasks/stale_containers.yaml
|
||||
vars:
|
||||
stale_containers_state: enabled
|
||||
@@ -63,7 +63,7 @@
|
||||
k3s_state: uninstalled
|
||||
|
||||
- name: Stale Containers
|
||||
ansible.builtin.include_tasks: tasks/stale_containers.yml
|
||||
ansible.builtin.include_tasks: tasks/stale_containers.yaml
|
||||
vars:
|
||||
stale_containers_state: disabled
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -17,7 +17,7 @@ spec:
|
||||
image:
|
||||
repository: registry.k8s.io/coredns/coredns
|
||||
tag: v1.10.1
|
||||
replicaCount: 3
|
||||
replicaCount: 1
|
||||
service:
|
||||
# Choose the 10th IP address from the start of the service-cidr
|
||||
clusterIP: {{ k3s_server['service-cidr'] | ansible.utils.nthhost(10) }}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
collections:
|
||||
- name: community.general
|
||||
version: 7.1.0
|
||||
- name: community.sops
|
||||
version: 1.6.4
|
||||
- name: ansible.posix
|
||||
version: 1.5.4
|
||||
- name: ansible.utils
|
||||
version: 2.10.3
|
||||
- name: community.general
|
||||
version: 7.1.0
|
||||
- name: kubernetes.core
|
||||
version: 2.4.0
|
||||
- name: community.sops
|
||||
version: 1.6.4
|
||||
roles:
|
||||
- name: xanmanning.k3s
|
||||
src: https://github.com/PyratLabs/ansible-role-k3s.git
|
||||
@@ -1,482 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck disable=SC2155
|
||||
export PROJECT_DIR=$(git rev-parse --show-toplevel)
|
||||
|
||||
# shellcheck disable=SC2155
|
||||
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "${PROJECT_DIR}/.config.env"
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") <options>
|
||||
-h, --help Display help
|
||||
--verify Verify .config.env settings
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
local verify=
|
||||
parse_command_line "$@"
|
||||
if [[ "${verify}" == 1 ]]; then
|
||||
verify_start
|
||||
verify_binaries
|
||||
verify_master_count
|
||||
verify_ansible_hosts
|
||||
verify_lbs
|
||||
verify_kubevip
|
||||
verify_cluster_service_cidrs
|
||||
verify_age
|
||||
verify_git_repository
|
||||
verify_cloudflare
|
||||
verify_success
|
||||
else
|
||||
# generate sops configuration file
|
||||
envsubst < "${PROJECT_DIR}/tmpl/.sops.yaml" \
|
||||
> "${PROJECT_DIR}/.sops.yaml"
|
||||
# generate cluster settings
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/flux/cluster-settings.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/flux/vars/cluster-settings.yaml"
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/flux/cluster.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/flux/config/cluster.yaml"
|
||||
# generate secrets
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/cluster-secrets.sops.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/flux/vars/cluster-secrets.sops.yaml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/flux/vars/cluster-secrets.sops.yaml"
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/cert-manager-secret.sops.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml"
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/cloudflared-secret.sops.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/apps/networking/cloudflared/app/secret.sops.yaml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/networking/cloudflared/app/secret.sops.yaml"
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/external-dns-secret.sops.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/apps/networking/external-dns/app/secret.sops.yaml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/networking/external-dns/app/secret.sops.yaml"
|
||||
# generate ansible settings
|
||||
envsubst < "${PROJECT_DIR}/tmpl/ansible/supplemental.yml" \
|
||||
> "${PROJECT_DIR}/ansible/inventory/group_vars/kubernetes/supplemental.yml"
|
||||
# generate ansible hosts file and secrets
|
||||
generate_ansible_hosts
|
||||
generate_ansible_host_secrets
|
||||
setup_github_webhook
|
||||
setup_weave_gitops
|
||||
setup_grafana_admin
|
||||
setup_prometheus_endpoints
|
||||
success
|
||||
fi
|
||||
}
|
||||
|
||||
parse_command_line() {
|
||||
while :; do
|
||||
case "${1:-}" in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit
|
||||
;;
|
||||
--verify)
|
||||
verify=1
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
|
||||
shift
|
||||
done
|
||||
if [[ -z "$verify" ]]; then
|
||||
verify=0
|
||||
fi
|
||||
}
|
||||
|
||||
_has_binary() {
|
||||
command -v "${1}" >/dev/null 2>&1 || {
|
||||
_log "ERROR(${FUNCNAME[0]})" "${1} is not installed or not found in \$PATH"
|
||||
exit 1
|
||||
}
|
||||
_log "INFO(${FUNCNAME[0]})" "Found CLI tool ${1} and it is in \$PATH"
|
||||
}
|
||||
|
||||
_has_optional_envar() {
|
||||
local option="${1}"
|
||||
# shellcheck disable=SC2015
|
||||
[[ "${!option}" == "" ]] && {
|
||||
_log "WARN" "Unset optional variable ${option}"
|
||||
} || {
|
||||
_log "INFO(${FUNCNAME[0]})" "Found variable '${option}' with value '${!option}'"
|
||||
}
|
||||
}
|
||||
|
||||
_has_envar() {
|
||||
local option="${1}"
|
||||
local secret="${2:-false}"
|
||||
local value=
|
||||
# shellcheck disable=SC2015
|
||||
if [[ "${!option}" == "" ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "Unset variable ${option}"
|
||||
exit 1
|
||||
fi
|
||||
value="${!option}"
|
||||
if [[ $secret == "true" ]]; then
|
||||
value="***"
|
||||
fi
|
||||
_log "INFO(${FUNCNAME[0]})" "Found variable '${option}' with value '${value}'"
|
||||
}
|
||||
|
||||
_has_valid_ip() {
|
||||
local ip="${1}"
|
||||
local variable_name="${2}"
|
||||
if ! ipcalc "${ip}" | awk 'BEGIN{FS=":"; is_invalid=0} /^INVALID/ {is_invalid=1; print $1} END{exit is_invalid}' >/dev/null 2>&1; then
|
||||
_log "INFO(${FUNCNAME[0]})" "Variable '${variable_name}' has an invalid IP address '${ip}'"
|
||||
exit 1
|
||||
else
|
||||
_log "INFO(${FUNCNAME[0]})" "Variable '${variable_name}' has a valid IP address '${ip}'"
|
||||
fi
|
||||
}
|
||||
|
||||
verify_age() {
|
||||
_has_envar "BOOTSTRAP_AGE_PUBLIC_KEY"
|
||||
_has_envar "SOPS_AGE_KEY_FILE"
|
||||
if [[ ! "$BOOTSTRAP_AGE_PUBLIC_KEY" =~ ^age.* ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "BOOTSTRAP_AGE_PUBLIC_KEY does not start with age"
|
||||
exit 1
|
||||
else
|
||||
_log "INFO(${FUNCNAME[0]})" "Age public key is in the correct format"
|
||||
fi
|
||||
if [[ ! -f ~/.config/sops/age/keys.txt ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "Unable to find Age file keys.txt in ~/.config/sops/age"
|
||||
exit 1
|
||||
else
|
||||
_log "INFO(${FUNCNAME[0]})" "Found Age public key '${BOOTSTRAP_AGE_PUBLIC_KEY}'"
|
||||
fi
|
||||
}
|
||||
|
||||
verify_binaries() {
|
||||
_has_binary "age"
|
||||
_has_binary "ansible"
|
||||
_has_binary "envsubst"
|
||||
_has_binary "flux"
|
||||
_has_binary "git"
|
||||
_has_binary "gitops"
|
||||
_has_binary "ipcalc"
|
||||
_has_binary "jq"
|
||||
_has_binary "pip3"
|
||||
_has_binary "sops"
|
||||
_has_binary "ssh"
|
||||
_has_binary "task"
|
||||
_has_binary "yq"
|
||||
if ! [[ "$(sops --version)" =~ 3\.[0-9]+\.[0-9]+ ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "Incompatible sops version, make sure you are using the latest release of github.com/mozilla/sops"
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$(yq --version)" =~ 4\.[0-9]+\.[0-9]+ ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "Incompatible yq version, make sure you are using the latest release of github.com/mikefarah/yq"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_kubevip() {
|
||||
_has_envar "BOOTSTRAP_KUBE_VIP_ADDR"
|
||||
_has_valid_ip "${BOOTSTRAP_KUBE_VIP_ADDR}" "BOOTSTRAP_KUBE_VIP_ADDR"
|
||||
}
|
||||
|
||||
verify_lbs() {
|
||||
#TODO: Validate IPs aren't already in use with arp
|
||||
_has_envar "BOOTSTRAP_K8S_GATEWAY_ADDR"
|
||||
_has_envar "BOOTSTRAP_INGRESS_NGINX_ADDR"
|
||||
_has_valid_ip "${BOOTSTRAP_K8S_GATEWAY_ADDR}" "BOOTSTRAP_K8S_GATEWAY_ADDR"
|
||||
_has_valid_ip "${BOOTSTRAP_INGRESS_NGINX_ADDR}" "BOOTSTRAP_INGRESS_NGINX_ADDR"
|
||||
}
|
||||
|
||||
verify_cluster_service_cidrs() {
|
||||
#TODO: Validate input is a CIDR range
|
||||
_has_envar "BOOTSTRAP_CLUSTER_CIDR"
|
||||
_has_envar "BOOTSTRAP_SERVICE_CIDR"
|
||||
}
|
||||
|
||||
verify_git_repository() {
|
||||
_has_envar "BOOTSTRAP_GIT_REPOSITORY"
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
pushd "$(mktemp -d)" >/dev/null 2>&1
|
||||
[ "$(git ls-remote "${BOOTSTRAP_GIT_REPOSITORY}" 2> /dev/null)" ] || {
|
||||
_log "ERROR(${FUNCNAME[0]})" "Unable to find the remote Git repository '${BOOTSTRAP_GIT_REPOSITORY}'"
|
||||
exit 1
|
||||
}
|
||||
popd >/dev/null 2>&1
|
||||
export GIT_TERMINAL_PROMPT=1
|
||||
}
|
||||
|
||||
verify_master_count() {
|
||||
local master_node_count=
|
||||
master_node_count=0
|
||||
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
||||
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
||||
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
||||
if [[ "${!node_control}" == "true" ]]; then
|
||||
master_node_count=$((master_node_count+1))
|
||||
fi
|
||||
done
|
||||
# Check for existance of master nodes and that their count is not an even number
|
||||
if [[ ${master_node_count} -eq 0 || $((master_node_count%2)) -eq 0 ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "You must have 1, 3 or an odd number of master nodes >=3 inorder for etcd to have quorum"
|
||||
exit 1
|
||||
fi
|
||||
_log "INFO(${FUNCNAME[0]})" "Verified control plane node count"
|
||||
}
|
||||
|
||||
verify_cloudflare() {
|
||||
local errors=
|
||||
local account_zone=
|
||||
_has_envar "BOOTSTRAP_CLOUDFLARE_APITOKEN" "true"
|
||||
_has_envar "BOOTSTRAP_CLOUDFLARE_DOMAIN" "true"
|
||||
_has_envar "BOOTSTRAP_ACME_EMAIL" "true"
|
||||
# Try to retrieve zone information from Cloudflare's API
|
||||
account_zone=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${BOOTSTRAP_CLOUDFLARE_DOMAIN}&status=active" \
|
||||
-H "Authorization: Bearer ${BOOTSTRAP_CLOUDFLARE_APITOKEN}" \
|
||||
-H "Content-Type: application/json"
|
||||
)
|
||||
if [[ "$(echo "${account_zone}" | jq ".success")" == "true" ]]; then
|
||||
_log "INFO(${FUNCNAME[0]})" "Verified Cloudflare Account and Zone information"
|
||||
else
|
||||
errors=$(echo "${account_zone}" | jq -c ".errors")
|
||||
_log "ERROR(${FUNCNAME[0]})" "Unable to get Cloudflare Account and Zone information ${errors}"
|
||||
exit 1
|
||||
fi
|
||||
local cloudflare_tunnel=
|
||||
_has_envar "BOOTSTRAP_CLOUDFLARE_ACCOUNT_TAG" "true"
|
||||
_has_envar "BOOTSTRAP_CLOUDFLARE_TUNNEL_SECRET" "true"
|
||||
_has_envar "BOOTSTRAP_CLOUDFLARE_TUNNEL_ID" "true"
|
||||
# Try to retrieve the Cloudflare Tunnel information from Cloudflare's API
|
||||
cloudflare_tunnel=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/${BOOTSTRAP_CLOUDFLARE_ACCOUNT_TAG}/cfd_tunnel/${BOOTSTRAP_CLOUDFLARE_TUNNEL_ID}" \
|
||||
-H "Authorization: Bearer ${BOOTSTRAP_CLOUDFLARE_APITOKEN}" \
|
||||
-H "Content-Type: application/json"
|
||||
)
|
||||
if [[ "$(echo "${cloudflare_tunnel}" | jq ".success")" == "true" ]]; then
|
||||
_log "INFO(${FUNCNAME[0]})" "Verified Cloudflare Tunnel information"
|
||||
else
|
||||
errors=$(echo "${cloudflare_tunnel}" | jq -c ".errors")
|
||||
_log "ERROR(${FUNCNAME[0]})" "Unable to get Cloudflare Tunnel information ${errors}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_ansible_hosts() {
|
||||
local node_id=
|
||||
local node_addr=
|
||||
local node_username=
|
||||
local node_password=
|
||||
local node_control=
|
||||
local node_hostname=
|
||||
local default_control_node_prefix=
|
||||
local default_worker_node_prefix=
|
||||
default_control_node_prefix="BOOTSTRAP_ANSIBLE_DEFAULT_CONTROL_NODE_HOSTNAME_PREFIX"
|
||||
default_worker_node_prefix="BOOTSTRAP_ANSIBLE_DEFAULT_NODE_HOSTNAME_PREFIX"
|
||||
_has_optional_envar "${default_control_node_prefix}"
|
||||
_has_optional_envar "${default_worker_node_prefix}"
|
||||
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
||||
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
||||
node_addr="BOOTSTRAP_ANSIBLE_HOST_ADDR_${node_id}"
|
||||
node_username="BOOTSTRAP_ANSIBLE_SSH_USERNAME_${node_id}"
|
||||
node_password="BOOTSTRAP_ANSIBLE_SUDO_PASSWORD_${node_id}"
|
||||
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
||||
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
||||
_has_envar "${node_addr}"
|
||||
_has_envar "${node_username}"
|
||||
_has_envar "${node_password}" "true"
|
||||
_has_envar "${node_control}"
|
||||
_has_optional_envar "${node_hostname}"
|
||||
if [[ "${!node_addr}" == "${BOOTSTRAP_KUBE_VIP_ADDR}" ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "The kube-vip IP '${BOOTSTRAP_KUBE_VIP_ADDR}' should not be the same as the IP for node '${!node_addr}'"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${!node_addr}" == "${BOOTSTRAP_K8S_GATEWAY_ADDR}" ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "The k8s-gateway load balancer IP '${BOOTSTRAP_K8S_GATEWAY_ADDR}' should not be the same as the IP for node '${!node_addr}'"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${!node_addr}" == "${BOOTSTRAP_INGRESS_NGINX_ADDR}" ]]; then
|
||||
_log "ERROR(${FUNCNAME[0]})" "The ingress load balancer IP '${BOOTSTRAP_INGRESS_NGINX_ADDR}' should not be the same as the IP for node '${!node_addr}'"
|
||||
exit 1
|
||||
fi
|
||||
if ssh -q -o BatchMode=yes -o ConnectTimeout=5 "${!node_username}"@"${!var}" "true"; then
|
||||
_log "INFO(${FUNCNAME[0]})" "SSH into host '${!var}' with username '${!node_username}' was successfull"
|
||||
else
|
||||
_log "ERROR(${FUNCNAME[0]})" "SSH into host '${!var}' with username '${!node_username}'was NOT successful, did you copy over your SSH key?"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
verify_start() {
|
||||
_log "INFO(${FUNCNAME[0]})" "Starting verification of .config.env, please wait..."
|
||||
}
|
||||
|
||||
verify_success() {
|
||||
_log "INFO(${FUNCNAME[0]})" "All checks passed!"
|
||||
_log "INFO(${FUNCNAME[0]})" "Run the script without --verify to template all the files out"
|
||||
exit 0
|
||||
}
|
||||
|
||||
generate_ansible_host_secrets() {
|
||||
local node_id=
|
||||
local node_username=
|
||||
local node_password=
|
||||
local node_hostname=
|
||||
local node_control=
|
||||
default_control_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_CONTROL_NODE_HOSTNAME_PREFIX:-k8s-}
|
||||
default_worker_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_NODE_HOSTNAME_PREFIX:-k8s-}
|
||||
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
||||
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
||||
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
||||
if [[ "${!node_control}" == "true" ]]; then
|
||||
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
||||
host_key="${!node_hostname:-${default_control_node_prefix}}"
|
||||
if [ "${host_key}" == "${default_control_node_prefix}" ]; then
|
||||
node_hostname=${default_control_node_prefix}${node_id}
|
||||
else
|
||||
node_hostname=${!node_hostname}
|
||||
fi
|
||||
else
|
||||
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
||||
host_key="${!node_hostname:-${default_worker_node_prefix}}"
|
||||
if [ "${host_key}" == "${default_worker_node_prefix}" ]; then
|
||||
node_hostname=${default_worker_node_prefix}${node_id}
|
||||
else
|
||||
node_hostname=${!node_hostname}
|
||||
fi
|
||||
fi
|
||||
{
|
||||
node_username="BOOTSTRAP_ANSIBLE_SSH_USERNAME_${node_id}"
|
||||
node_password="BOOTSTRAP_ANSIBLE_SUDO_PASSWORD_${node_id}"
|
||||
printf "kind: Secret\n"
|
||||
printf "ansible_user: %s\n" "${!node_username}"
|
||||
printf "ansible_become_pass: %s\n" "${!node_password}"
|
||||
} > "${PROJECT_DIR}/ansible/inventory/host_vars/${node_hostname}.sops.yml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/ansible/inventory/host_vars/${node_hostname}.sops.yml"
|
||||
done
|
||||
}
|
||||
|
||||
generate_ansible_hosts() {
|
||||
local worker_node_count=
|
||||
default_control_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_CONTROL_NODE_HOSTNAME_PREFIX:-k8s-}
|
||||
default_worker_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_NODE_HOSTNAME_PREFIX:-k8s-}
|
||||
{
|
||||
printf -- "---\n"
|
||||
printf "kubernetes:\n"
|
||||
printf " children:\n"
|
||||
printf " master:\n"
|
||||
printf " hosts:\n"
|
||||
master_node_count=0
|
||||
worker_node_count=0
|
||||
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
||||
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
||||
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
||||
if [[ "${!node_control}" == "true" ]]; then
|
||||
master_node_count=$((master_node_count+1))
|
||||
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
||||
host_key="${!node_hostname:-${default_control_node_prefix}}"
|
||||
if [ "${host_key}" == "${default_control_node_prefix}" ]; then
|
||||
node_hostname=${default_control_node_prefix}${node_id}
|
||||
else
|
||||
node_hostname=${!node_hostname}
|
||||
fi
|
||||
printf " %s:\n" "${node_hostname}"
|
||||
printf " ansible_host: %s\n" "${!var}"
|
||||
else
|
||||
worker_node_count=$((worker_node_count+1))
|
||||
fi
|
||||
done
|
||||
if [[ ${worker_node_count} -gt 0 ]]; then
|
||||
printf " worker:\n"
|
||||
printf " hosts:\n"
|
||||
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
||||
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
||||
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
||||
if [[ "${!node_control}" == "false" ]]; then
|
||||
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
||||
host_key="${!node_hostname:-${default_worker_node_prefix}}"
|
||||
if [ "${host_key}" == "${default_worker_node_prefix}" ]; then
|
||||
node_hostname=${default_worker_node_prefix}${node_id}
|
||||
else
|
||||
node_hostname=${!node_hostname}
|
||||
fi
|
||||
printf " %s:\n" "${node_hostname}"
|
||||
printf " ansible_host: %s\n" "${!var}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
} > "${PROJECT_DIR}/ansible/inventory/hosts.yml"
|
||||
}
|
||||
|
||||
setup_prometheus_endpoints() {
|
||||
yq --inplace 'del(.spec.values.kubeControllerManager.endpoints[])' \
|
||||
"${PROJECT_DIR}/kubernetes/apps/monitoring/kube-prometheus-stack/app/helmrelease.yaml"
|
||||
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
||||
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
||||
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
||||
if [[ "${!node_control}" == "true" ]]; then
|
||||
node_addr="BOOTSTRAP_ANSIBLE_HOST_ADDR_${node_id}"
|
||||
_log "INFO(${FUNCNAME[0]})" "Setting up Prometheus endpoint for '${!node_addr}'"
|
||||
yq --inplace ".spec.values.kubeControllerManager.endpoints += \"${!node_addr}\"" \
|
||||
"${PROJECT_DIR}/kubernetes/apps/monitoring/kube-prometheus-stack/app/helmrelease.yaml"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
setup_github_webhook() {
|
||||
_has_envar "BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET"
|
||||
WEBHOOK_SECRET="${BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET}"
|
||||
if [[ "${WEBHOOK_SECRET}" == "generated" ]]; then
|
||||
WEBHOOK_SECRET="$(openssl rand -hex 12)"
|
||||
fi
|
||||
export BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET="${WEBHOOK_SECRET}"
|
||||
_log "INFO(${FUNCNAME[0]})" "Using GitHub Token '${WEBHOOK_SECRET}' for Flux"
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/github-webhook-token-secret.sops.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/apps/flux-system/addons/webhooks/github/secret.sops.yaml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/flux-system/addons/webhooks/github/secret.sops.yaml"
|
||||
}
|
||||
|
||||
setup_weave_gitops() {
|
||||
_has_envar "BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD"
|
||||
WEAVE_GITOPS_ADMIN_PASSWORD="${BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD}"
|
||||
if [[ "${WEAVE_GITOPS_ADMIN_PASSWORD}" == "generated" ]]; then
|
||||
WEAVE_GITOPS_ADMIN_PASSWORD="$(openssl rand -hex 12)"
|
||||
fi
|
||||
export BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD="${WEAVE_GITOPS_ADMIN_PASSWORD}"
|
||||
_log "INFO(${FUNCNAME[0]})" "Using admin password '${WEAVE_GITOPS_ADMIN_PASSWORD}' for Weave Gitops"
|
||||
# Convert password to bcrypt hash
|
||||
# shellcheck disable=SC2155
|
||||
export BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD="$(echo -n "${BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD}" | gitops get bcrypt-hash)"
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/weave-gitops-secret.sops.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/apps/flux-system/weave-gitops/app/secret.sops.yaml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/flux-system/weave-gitops/app/secret.sops.yaml"
|
||||
}
|
||||
|
||||
setup_grafana_admin() {
|
||||
_has_envar "BOOTSTRAP_GRAFANA_ADMIN_PASSWORD"
|
||||
GRAFANA_ADMIN_PASSWORD="${BOOTSTRAP_GRAFANA_ADMIN_PASSWORD}"
|
||||
if [[ "${GRAFANA_ADMIN_PASSWORD}" == "generated" ]]; then
|
||||
GRAFANA_ADMIN_PASSWORD="$(openssl rand -hex 12)"
|
||||
fi
|
||||
export BOOTSTRAP_GRAFANA_ADMIN_PASSWORD="${GRAFANA_ADMIN_PASSWORD}"
|
||||
_log "INFO(${FUNCNAME[0]})" "Using password '${GRAFANA_ADMIN_PASSWORD}' for Grafana"
|
||||
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/grafana-admin-secret.sops.yaml" \
|
||||
> "${PROJECT_DIR}/kubernetes/apps/monitoring/grafana/app/secret.sops.yaml"
|
||||
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/monitoring/grafana/app/secret.sops.yaml"
|
||||
}
|
||||
|
||||
success() {
|
||||
_log "INFO(${FUNCNAME[0]})" "All files have been templated, proceed to the next steps outlined in the README"
|
||||
exit 0
|
||||
}
|
||||
|
||||
_log() {
|
||||
local type="${1}"
|
||||
local msg="${2}"
|
||||
printf 'timestamp="%s" type="%s" message="%s"\n' "$(date)" "${type}" "${msg}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta1
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: coredns
|
||||
namespace: kube-system
|
||||
spec:
|
||||
interval: 30m
|
||||
chart:
|
||||
spec:
|
||||
chart: coredns
|
||||
version: 1.24.1
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: coredns
|
||||
namespace: flux-system
|
||||
maxHistory: 2
|
||||
install:
|
||||
createNamespace: true
|
||||
remediation:
|
||||
retries: 3
|
||||
upgrade:
|
||||
cleanupOnFail: true
|
||||
remediation:
|
||||
retries: 3
|
||||
uninstall:
|
||||
keepHistory: false
|
||||
values:
|
||||
fullnameOverride: coredns
|
||||
image:
|
||||
repository: registry.k8s.io/coredns/coredns
|
||||
tag: v1.10.1
|
||||
replicaCount: 1
|
||||
service:
|
||||
clusterIP: "${COREDNS_ADDR}"
|
||||
serviceAccount:
|
||||
create: true
|
||||
deployment:
|
||||
annotations:
|
||||
reloader.stakater.com/auto: "true"
|
||||
servers:
|
||||
- zones:
|
||||
- zone: .
|
||||
scheme: dns://
|
||||
use_tcp: true
|
||||
port: 53
|
||||
plugins:
|
||||
- name: log
|
||||
- name: errors
|
||||
- name: health
|
||||
configBlock: |-
|
||||
lameduck 5s
|
||||
- name: ready
|
||||
- name: kubernetes
|
||||
parameters: cluster.local in-addr.arpa ip6.arpa
|
||||
configBlock: |-
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
- name: prometheus
|
||||
parameters: 0.0.0.0:9153
|
||||
- name: forward
|
||||
parameters: . /etc/resolv.conf
|
||||
- name: cache
|
||||
parameters: 30
|
||||
- name: loop
|
||||
- name: reload
|
||||
- name: loadbalance
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
- key: node-role.kubernetes.io/master
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: DoNotSchedule
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: coredns
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: kube-system
|
||||
resources:
|
||||
- ./helmrelease.yaml
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: cluster-apps-coredns
|
||||
namespace: flux-system
|
||||
spec:
|
||||
path: ./kubernetes/apps/kube-system/coredns/app
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: home-kubernetes
|
||||
wait: false # no flux ks dependents
|
||||
interval: 30m
|
||||
retryInterval: 1m
|
||||
timeout: 5m
|
||||
@@ -4,6 +4,7 @@ kind: Kustomization
|
||||
resources:
|
||||
- ./namespace.yaml
|
||||
- ./cilium/ks.yaml
|
||||
- ./coredns/ks.yaml
|
||||
- ./local-path-provisioner/ks.yaml
|
||||
- ./metrics-server/ks.yaml
|
||||
- ./reloader/ks.yaml
|
||||
@@ -5,5 +5,5 @@ resources:
|
||||
- ./namespace.yaml
|
||||
# NOTE: Only enable system-upgrade-controller if you also update the version of k3s in the following file
|
||||
# otherwise if you run ansible against an already provisioned cluster you might downgrade it
|
||||
# https://github.com/onedr0p/flux-cluster-template/blob/main/ansible/inventory/group_vars/kubernetes/k3s.yml
|
||||
# https://github.com/onedr0p/flux-cluster-template/blob/main/ansible/inventory/group_vars/kubernetes/main.yaml
|
||||
# - ./system-upgrade-controller/ks.yaml
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
- name: Cluster Installation
|
||||
hosts: localhost
|
||||
connection: local
|
||||
vars_files:
|
||||
- vars/config.yaml
|
||||
- defaults/templates.yaml
|
||||
- defaults/extras.yaml
|
||||
tasks:
|
||||
- name: Get absolute path to this Git repository # noqa: command-instead-of-module
|
||||
ansible.builtin.command: git rev-parse --show-toplevel
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
register: repository_path
|
||||
failed_when: repository_path.rc != 0
|
||||
|
||||
- name: Verify configuration
|
||||
ansible.builtin.include_tasks: tasks/verify_{{ task }}.yaml
|
||||
loop: [age, cli, net, cloudflare, github]
|
||||
loop_control:
|
||||
loop_var: task
|
||||
|
||||
- name: Ensure template directories exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ repository_path.stdout }}/{{ item.dest | dirname }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop: "{{ templates }}"
|
||||
|
||||
- name: Template out non-encrypted files
|
||||
ansible.builtin.template:
|
||||
src: "templates/{{ item.src }}"
|
||||
dest: "{{ repository_path.stdout }}/{{ item.dest }}"
|
||||
mode: "0755"
|
||||
loop: "{{ templates | selectattr('encrypt', 'equalto', false) | list }}"
|
||||
|
||||
# https://github.com/ansible-collections/community.sops/issues/153
|
||||
- name: Backup sops configuration file
|
||||
ansible.builtin.copy:
|
||||
src: "{{ repository_path.stdout }}/.sops.yaml"
|
||||
dest: "{{ repository_path.stdout }}/.sops.yaml.orig"
|
||||
mode: "0755"
|
||||
- name: Remove sops configuration file
|
||||
ansible.builtin.file:
|
||||
path: "{{ repository_path.stdout }}/.sops.yaml"
|
||||
state: absent
|
||||
|
||||
- name: Template out encrypted files
|
||||
community.sops.sops_encrypt:
|
||||
path: "{{ repository_path.stdout }}/{{ item.dest }}"
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
age: ["{{ bootstrap_age_public_key }}"]
|
||||
content_yaml: "{{ lookup('ansible.builtin.template', 'templates/' + item.src) | from_yaml }}"
|
||||
mode: "0755"
|
||||
force: true
|
||||
loop: "{{ templates | selectattr('encrypt', 'equalto', true) | list }}"
|
||||
|
||||
- name: Ensure Ansible inventory directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ repository_path.stdout }}/ansible/inventory/host_vars"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Template out node secrets
|
||||
community.sops.sops_encrypt:
|
||||
path: "{{ repository_path.stdout }}/ansible/inventory/host_vars/{{ item.name }}.sops.yaml"
|
||||
age: ["{{ bootstrap_age_public_key }}"]
|
||||
content_yaml: "{{ lookup('ansible.builtin.template', 'templates/ansible/node.sops.yaml.j2', template_vars=dict(password=item.password)) | from_yaml }}"
|
||||
mode: "0755"
|
||||
force: true
|
||||
loop: "{{ bootstrap_hosts.master + bootstrap_hosts.worker | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.address }}"
|
||||
|
||||
# https://github.com/ansible-collections/community.sops/issues/153
|
||||
- name: Restore sops configuration file
|
||||
ansible.builtin.copy:
|
||||
src: "{{ repository_path.stdout }}/.sops.yaml.orig"
|
||||
dest: "{{ repository_path.stdout }}/.sops.yaml"
|
||||
mode: "0755"
|
||||
- name: Remove backup sops configuration file
|
||||
ansible.builtin.file:
|
||||
path: "{{ repository_path.stdout }}/.sops.yaml.orig"
|
||||
state: absent
|
||||
@@ -0,0 +1,2 @@
|
||||
---
|
||||
bootstrap_coredns_addr: "{{ bootstrap_service_cidr | ansible.utils.nthhost(10) }}"
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
templates:
|
||||
# Root
|
||||
- src: .sops.yaml.j2
|
||||
dest: ./.sops.yaml
|
||||
encrypt: false
|
||||
# Ansible dir
|
||||
- src: ansible/supplemental.yaml.j2
|
||||
dest: ./ansible/inventory/group_vars/kubernetes/supplemental.yaml
|
||||
encrypt: false
|
||||
- src: ansible/hosts.yaml.j2
|
||||
dest: ./ansible/hosts.yaml
|
||||
encrypt: false
|
||||
# Kubernetes dir
|
||||
- src: kubernetes/kube-prometheus-stack-helmrelease.yaml.j2
|
||||
dest: ./kubernetes/apps/monitoring/kube-prometheus-stack/app/helmrelease.yaml
|
||||
encrypt: false
|
||||
- src: kubernetes/cert-manager-secret.sops.yaml.j2
|
||||
dest: ./kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml
|
||||
encrypt: true
|
||||
- src: kubernetes/cloudflared-secret.sops.yaml.j2
|
||||
dest: ./kubernetes/apps/networking/cloudflared/app/secret.sops.yaml
|
||||
encrypt: true
|
||||
- src: kubernetes/cluster-secrets.sops.yaml.j2
|
||||
dest: ./kubernetes/flux/vars/cluster-secrets.sops.yaml
|
||||
encrypt: true
|
||||
- src: kubernetes/cluster-settings.yaml.j2
|
||||
dest: ./kubernetes/flux/vars/cluster-settings.yaml
|
||||
encrypt: false
|
||||
- src: kubernetes/cluster.yaml.j2
|
||||
dest: ./kubernetes/flux/config/cluster.yaml
|
||||
encrypt: false
|
||||
- src: kubernetes/external-dns-secret.sops.yaml.j2
|
||||
dest: ./kubernetes/apps/networking/external-dns/app/secret.sops.yaml
|
||||
encrypt: true
|
||||
- src: kubernetes/github-webhook-token-secret.sops.yaml.j2
|
||||
dest: ./kubernetes/apps/flux-system/addons/webhooks/github/secret.sops.yaml
|
||||
encrypt: true
|
||||
- src: kubernetes/grafana-admin-secret.sops.yaml.j2
|
||||
dest: ./kubernetes/apps/monitoring/grafana/app/secret.sops.yaml
|
||||
encrypt: true
|
||||
- src: kubernetes/weave-gitops-secret.sops.yaml.j2
|
||||
dest: ./kubernetes/apps/flux-system/weave-gitops/app/secret.sops.yaml
|
||||
encrypt: true
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
- name: Query age key file
|
||||
ansible.builtin.stat:
|
||||
path: ~/.config/sops/age/keys.txt
|
||||
register: result
|
||||
|
||||
- name: Check if age key file exists
|
||||
ansible.builtin.assert:
|
||||
that: [result.stat.exists]
|
||||
success_msg: Age file ~/.config/sops/age/keys.txt exists
|
||||
fail_msg: Age file ~/.config/sops/age/keys.txt does not exist
|
||||
|
||||
- name: Query age key file contents
|
||||
ansible.builtin.set_fact:
|
||||
age_contents: "{{ lookup('file', '~/.config/sops/age/keys.txt') }}"
|
||||
|
||||
- name: Check if age public keys match
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'{{ bootstrap_age_public_key }}' in age_contents"
|
||||
success_msg: Age public key {{ bootstrap_age_public_key }} exists
|
||||
fail_msg: Age public key {{ bootstrap_age_public_key }} does not exist
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Check for required CLI tools
|
||||
ansible.builtin.command: "command -v {{ item }}"
|
||||
loop: [age, flux, git, pip3, sops, task]
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
register: result
|
||||
failed_when: result.rc != 0 and result.rc != 127
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
- name: Query Cloudflare zone
|
||||
ansible.builtin.uri:
|
||||
url: "https://api.cloudflare.com/client/v4/zones?name={{ bootstrap_cloudflare_domain }}&status=active"
|
||||
headers:
|
||||
Authorization: "Bearer {{ bootstrap_cloudflare_token }}"
|
||||
Content-Type: application/json
|
||||
timeout: 5
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: result
|
||||
|
||||
- name: Check if Cloudflare zone exists
|
||||
ansible.builtin.assert:
|
||||
that: ["result.json.success == true"]
|
||||
success_msg: "Cloudflare zone {{ bootstrap_cloudflare_domain }} exists"
|
||||
fail_msg: "Cloudflare zone {{ bootstrap_cloudflare_domain }} does not exist"
|
||||
|
||||
- name: Query Cloudflared tunnel
|
||||
ansible.builtin.uri:
|
||||
url: "https://api.cloudflare.com/client/v4/accounts/{{ bootstrap_cloudflare_account_tag }}/cfd_tunnel/{{ bootstrap_cloudflare_tunnel_id }}"
|
||||
headers:
|
||||
Authorization: "Bearer {{ bootstrap_cloudflare_token }}"
|
||||
Content-Type: application/json
|
||||
timeout: 5
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: result
|
||||
|
||||
- name: Check if Cloudflared tunnel exists
|
||||
ansible.builtin.assert:
|
||||
that: ["result.json.success == true"]
|
||||
success_msg: "Cloudflared tunnel {{ bootstrap_cloudflare_tunnel_id }} exists"
|
||||
fail_msg: "Cloudflared tunnel {{ bootstrap_cloudflare_tunnel_id }} does not exist"
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: Query Github username
|
||||
ansible.builtin.uri:
|
||||
url: "https://api.github.com/users/{{ bootstrap_github_username }}"
|
||||
timeout: 5
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: result
|
||||
|
||||
- name: Check if username exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "result.json.login == '{{ bootstrap_github_username }}'"
|
||||
success_msg: "Github user {{ bootstrap_github_username }} exists"
|
||||
fail_msg: "Github user {{ bootstrap_github_username }} does not exist"
|
||||
|
||||
- name: Query Github repo
|
||||
ansible.builtin.uri:
|
||||
url: "https://api.github.com/repos/{{ bootstrap_github_username }}/{{ bootstrap_github_repository_name }}"
|
||||
timeout: 5
|
||||
return_content: true
|
||||
body_format: json
|
||||
register: result
|
||||
|
||||
- name: Check if repo exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "result.json.full_name == '{{ bootstrap_github_username }}/{{ bootstrap_github_repository_name }}'"
|
||||
success_msg: "Github repo {{ bootstrap_github_username }}/{{ bootstrap_github_repository_name }} exists"
|
||||
fail_msg: "Github repo {{ bootstrap_github_username }}/{{ bootstrap_github_repository_name }} does not exist"
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
- name: Verify master node count
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "{{ bootstrap_hosts.master | length > 0 }}"
|
||||
- "{{ bootstrap_hosts.master | length is odd }}"
|
||||
success_msg: "Master node count {{ bootstrap_hosts.master | length }} is correct."
|
||||
fail_msg: "Master node count {{ bootstrap_hosts.master | length }} is not greater than 0 or is not odd."
|
||||
|
||||
- name: Verify node CIDR
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_node_cidr is ansible.utils.ipv4 }}"]
|
||||
success_msg: "Node CIDR {{ bootstrap_node_cidr }} is valid."
|
||||
fail_msg: "Node CIDR {{ bootstrap_node_cidr }} is invalid."
|
||||
|
||||
- name: Verify cluster CIDR
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_cluster_cidr is ansible.utils.ipv4 }}"]
|
||||
success_msg: "Cluster CIDR {{ bootstrap_cluster_cidr }} is valid."
|
||||
fail_msg: "Cluster CIDR {{ bootstrap_cluster_cidr }} is invalid."
|
||||
|
||||
- name: Verify service CIDR
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_service_cidr is ansible.utils.ipv4 }}"]
|
||||
success_msg: "Service CIDR {{ bootstrap_service_cidr }} is valid."
|
||||
fail_msg: "Service CIDR {{ bootstrap_service_cidr }} is invalid."
|
||||
|
||||
- name: Verify k8s_gateway
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_k8s_gateway_addr is ansible.utils.ipv4 }}"]
|
||||
success_msg: "k8s_gateway address {{ bootstrap_k8s_gateway_addr }} is valid."
|
||||
fail_msg: "k8s_gateway address {{ bootstrap_k8s_gateway_addr }} is invalid."
|
||||
|
||||
- name: Verify k8s_gateway in node CIDR
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_node_cidr | ansible.utils.network_in_usable(bootstrap_k8s_gateway_addr) }}"]
|
||||
success_msg: "k8s_gateway address {{ bootstrap_k8s_gateway_addr }} is within {{ bootstrap_node_cidr }}."
|
||||
fail_msg: "k8s_gateway address {{ bootstrap_k8s_gateway_addr }} is not within {{ bootstrap_node_cidr }}."
|
||||
|
||||
- name: Verify ingress-nginx
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_ingress_nginx_addr is ansible.utils.ipv4 }}"]
|
||||
success_msg: "ingress-nginx address {{ bootstrap_ingress_nginx_addr }} is valid."
|
||||
fail_msg: "ingress-nginx address {{ bootstrap_ingress_nginx_addr }} is invalid."
|
||||
|
||||
- name: Verify ingress-nginx in node CIDR
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_node_cidr | ansible.utils.network_in_usable(bootstrap_ingress_nginx_addr) }}"]
|
||||
success_msg: "ingress-nginx address {{ bootstrap_ingress_nginx_addr }} is within {{ bootstrap_node_cidr }}."
|
||||
fail_msg: "ingress-nginx address {{ bootstrap_ingress_nginx_addr }} is not within {{ bootstrap_node_cidr }}."
|
||||
|
||||
- name: Verify kube-vip
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_kube_vip_addr is ansible.utils.ipv4 }}"]
|
||||
success_msg: "kube-vip address {{ bootstrap_kube_vip_addr }} is valid."
|
||||
fail_msg: "kube-vip address {{ bootstrap_kube_vip_addr }} is invalid."
|
||||
|
||||
- name: Verify kube-vip in node CIDR
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_node_cidr | ansible.utils.network_in_usable(bootstrap_kube_vip_addr) }}"]
|
||||
success_msg: "kube-vip address {{ bootstrap_kube_vip_addr }} is within {{ bootstrap_node_cidr }}."
|
||||
fail_msg: "kube-vip address {{ bootstrap_kube_vip_addr }} is not within {{ bootstrap_node_cidr }}."
|
||||
|
||||
- name: Verify nodes are not the same IPs as k8s_gateway, ingress-nginx or kube-vip
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "{{ item.address not in (bootstrap_k8s_gateway_addr, bootstrap_ingress_nginx_addr, bootstrap_kube_vip_addr) }}"
|
||||
success_msg: "Node address {{ item.address }} is different than k8s_gateway, ingress-nginx or kube-vip."
|
||||
fail_msg: "Node address {{ item.address }} is not different than k8s_gateway, ingress-nginx or kube-vip."
|
||||
quiet: true
|
||||
loop: "{{ bootstrap_hosts.master + bootstrap_hosts.worker | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.address }}"
|
||||
|
||||
- name: Verify nodes are in node CIDR
|
||||
ansible.builtin.assert:
|
||||
that: ["{{ bootstrap_node_cidr | ansible.utils.network_in_usable(item.address) }}"]
|
||||
success_msg: "Node address {{ item.address }} is within {{ bootstrap_node_cidr }}."
|
||||
fail_msg: "Node address {{ item.address }} is not within {{ bootstrap_node_cidr }}."
|
||||
quiet: true
|
||||
loop: "{{ bootstrap_hosts.master + bootstrap_hosts.worker | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.address }}"
|
||||
|
||||
- name: Verify SSH port is reachable
|
||||
ansible.builtin.wait_for:
|
||||
host: "{{ item.address }}"
|
||||
port: 22
|
||||
search_regex: OpenSSH
|
||||
timeout: 10
|
||||
connection: local
|
||||
loop: "{{ bootstrap_hosts.master + bootstrap_hosts.worker | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.address }}"
|
||||
@@ -4,8 +4,8 @@ creation_rules:
|
||||
encrypted_regex: "^(data|stringData)$"
|
||||
key_groups:
|
||||
- age:
|
||||
- ${BOOTSTRAP_AGE_PUBLIC_KEY}
|
||||
- "{{ bootstrap_age_public_key }}"
|
||||
- path_regex: ansible/.*\.sops\.ya?ml
|
||||
key_groups:
|
||||
- age:
|
||||
- ${BOOTSTRAP_AGE_PUBLIC_KEY}
|
||||
- "{{ bootstrap_age_public_key }}"
|
||||
@@ -0,0 +1,20 @@
|
||||
#jinja2: trim_blocks: True, lstrip_blocks: True
|
||||
---
|
||||
kubernetes:
|
||||
children:
|
||||
master:
|
||||
hosts:
|
||||
{% for item in bootstrap_hosts.master %}
|
||||
{{ item.name }}:
|
||||
ansible_user: {{ item.username }}
|
||||
ansible_host: {{ item.address }}
|
||||
{% endfor %}
|
||||
{% if bootstrap_hosts.worker | default([]) | length > 0 %}
|
||||
worker:
|
||||
hosts:
|
||||
{% for item in bootstrap_hosts.worker %}
|
||||
{{ item.name }}:
|
||||
ansible_user: {{ item.username }}
|
||||
ansible_host: {{ item.address }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,2 @@
|
||||
---
|
||||
ansible_become_pass: "{{ password }}"
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
timezone: "{{ bootstrap_timezone }}"
|
||||
coredns_addr: "{{ bootstrap_coredns_addr }}"
|
||||
kube_vip_addr: "{{ bootstrap_kube_vip_addr }}"
|
||||
cluster_cidr: "{{ bootstrap_cluster_cidr }}"
|
||||
service_cidr: "{{ bootstrap_service_cidr }}"
|
||||
+1
-1
@@ -5,4 +5,4 @@ metadata:
|
||||
name: cert-manager-secret
|
||||
namespace: cert-manager
|
||||
stringData:
|
||||
api-token: "${BOOTSTRAP_CLOUDFLARE_APITOKEN}"
|
||||
api-token: "{{ bootstrap_cloudflare_token }}"
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: cloudflared-secret
|
||||
namespace: networking
|
||||
stringData:
|
||||
TUNNEL_ID: "{{ bootstrap_cloudflare_tunnel_id }}"
|
||||
credentials.json: |
|
||||
{
|
||||
"AccountTag": "{{ bootstrap_cloudflare_account_tag }}",
|
||||
"TunnelSecret": "{{ bootstrap_cloudflare_tunnel_secret }}",
|
||||
"TunnelID": "{{ bootstrap_cloudflare_tunnel_id }}"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: cluster-secrets
|
||||
namespace: flux-system
|
||||
stringData:
|
||||
SECRET_DOMAIN: "{{ bootstrap_cloudflare_domain }}"
|
||||
SECRET_ACME_EMAIL: "{{ bootstrap_acme_email }}"
|
||||
SECRET_CLOUDFLARE_TUNNEL_ID: "{{ bootstrap_cloudflare_tunnel_id }}"
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cluster-settings
|
||||
namespace: flux-system
|
||||
data:
|
||||
TIMEZONE: "{{ bootstrap_timezone }}"
|
||||
COREDNS_ADDR: "{{ bootstrap_coredns_addr }}"
|
||||
K8S_GATEWAY_ADDR: "{{ bootstrap_k8s_gateway_addr }}"
|
||||
INGRESS_NGINX_ADDR: "{{ bootstrap_ingress_nginx_addr }}"
|
||||
KUBE_VIP_ADDR: "{{ bootstrap_kube_vip_addr }}"
|
||||
CLUSTER_CIDR: "{{ bootstrap_cluster_cidr }}"
|
||||
SERVICE_CIDR: "{{ bootstrap_service_cidr }}"
|
||||
@@ -8,7 +8,7 @@ spec:
|
||||
interval: 30m
|
||||
ref:
|
||||
branch: main
|
||||
url: "${BOOTSTRAP_GIT_REPOSITORY}"
|
||||
url: "https://github.com/{{ bootstrap_github_username }}/{{ bootstrap_github_repository_name }}.git"
|
||||
ignore: |
|
||||
# exclude all
|
||||
/*
|
||||
+1
-1
@@ -5,4 +5,4 @@ metadata:
|
||||
name: external-dns-secret
|
||||
namespace: networking
|
||||
stringData:
|
||||
api-token: "${BOOTSTRAP_CLOUDFLARE_APITOKEN}"
|
||||
api-token: "{{ bootstrap_cloudflare_token }}"
|
||||
+1
-1
@@ -5,4 +5,4 @@ metadata:
|
||||
name: github-webhook-token-secret
|
||||
namespace: flux-system
|
||||
stringData:
|
||||
token: "${BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET}"
|
||||
token: "{{ bootstrap_flux_github_webhook_token }}"
|
||||
+1
-1
@@ -6,4 +6,4 @@ metadata:
|
||||
namespace: monitoring
|
||||
stringData:
|
||||
admin-user: admin
|
||||
admin-password: "${BOOTSTRAP_GRAFANA_ADMIN_PASSWORD}"
|
||||
admin-password: "{{ bootstrap_grafana_admin_password }}"
|
||||
+4
-1
@@ -1,3 +1,4 @@
|
||||
#jinja2: trim_blocks: True, lstrip_blocks: True
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2beta1
|
||||
kind: HelmRelease
|
||||
@@ -88,7 +89,9 @@ spec:
|
||||
kubeControllerManager:
|
||||
enabled: true
|
||||
endpoints: &cp
|
||||
- 127.0.0.1 # Replaced by configure
|
||||
{% for item in bootstrap_hosts.master %}
|
||||
- {{ item.address }}
|
||||
{% endfor %}
|
||||
serviceMonitor:
|
||||
metricRelabelings:
|
||||
# Remove duplicate metrics
|
||||
+1
-1
@@ -7,4 +7,4 @@ metadata:
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: admin
|
||||
password: "${BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD}"
|
||||
password: "{{ bootstrap_weave_gitops_admin_password | password_hash('bcrypt') }}"
|
||||
@@ -0,0 +1 @@
|
||||
config.yaml
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
#
|
||||
# Bootstrap configuration - This file is gitignored
|
||||
#
|
||||
|
||||
# Github username (e.g. onedr0p)
|
||||
bootstrap_github_username:
|
||||
# Github repository (e.g. flux-cluster-template)
|
||||
bootstrap_github_repository_name:
|
||||
# Age Public Key (e.g. age15uzrw396e67z9wdzsxzdk7ka0g2gr3l460e0slaea563zll3hdfqwqxdta)
|
||||
bootstrap_age_public_key:
|
||||
# Choose your timezone (e.g. America/New_York)
|
||||
bootstrap_timezone: Etc/UTC
|
||||
# Email you want to be associated with the ACME account (used for TLS certificates via letsencrypt.org)
|
||||
bootstrap_acme_email:
|
||||
|
||||
# Flux github webhook token (openssl rand -hex 12)
|
||||
bootstrap_flux_github_webhook_token:
|
||||
# Grafana admin password (openssl rand -hex 12)
|
||||
bootstrap_grafana_admin_password:
|
||||
# Weave GitOps dashboard password (openssl rand -hex 12)
|
||||
bootstrap_weave_gitops_admin_password:
|
||||
|
||||
# Cloudflare domain
|
||||
bootstrap_cloudflare_domain:
|
||||
# Cloudflare API Token (not API Key)
|
||||
bootstrap_cloudflare_token:
|
||||
# Cloudflared Account Tag (cat ~/.cloudflared/*.json | jq -r .AccountTag)
|
||||
bootstrap_cloudflare_account_tag:
|
||||
# Cloudflared Tunnel Secret (cat ~/.cloudflared/*.json | jq -r .TunnelSecret)
|
||||
bootstrap_cloudflare_tunnel_secret:
|
||||
# Cloudflared Tunnel ID (cat ~/.cloudflared/*.json | jq -r .TunnelID)
|
||||
bootstrap_cloudflare_tunnel_id:
|
||||
|
||||
# CIDR your nodes are on (e.g. 192.168.1.0/24)
|
||||
bootstrap_node_cidr:
|
||||
# Load balancer IP for k8s_gateway, choose an available IP in your nodes network that is not being used
|
||||
bootstrap_k8s_gateway_addr:
|
||||
# Load balancer IP for ingress-nginx, choose an available IP in your nodes network that is not being used
|
||||
bootstrap_ingress_nginx_addr:
|
||||
# IP address to use with kube-vip, choose an available IP in your nodes network that is not being used
|
||||
bootstrap_kube_vip_addr:
|
||||
# Choose your cluster and service cidrs, leave these unchanged unless you know what you are doing
|
||||
bootstrap_cluster_cidr: 10.42.0.0/16
|
||||
bootstrap_service_cidr: 10.43.0.0/16
|
||||
|
||||
# Node information
|
||||
bootstrap_hosts:
|
||||
# Use only 1, 3 or more odd master nodes, recommended is 3
|
||||
master:
|
||||
# - name: kube0
|
||||
# address:
|
||||
# username:
|
||||
# password:
|
||||
worker:
|
||||
# - name: kube1
|
||||
# address:
|
||||
# username:
|
||||
# password:
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
timezone: "${BOOTSTRAP_TIMEZONE}"
|
||||
kube_vip_addr: "${BOOTSTRAP_KUBE_VIP_ADDR}"
|
||||
cluster_cidr: "${BOOTSTRAP_CLUSTER_CIDR}"
|
||||
service_cidr: "${BOOTSTRAP_SERVICE_CIDR}"
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: cloudflared-secret
|
||||
namespace: networking
|
||||
stringData:
|
||||
TUNNEL_ID: "${BOOTSTRAP_CLOUDFLARE_TUNNEL_ID}"
|
||||
credentials.json: |
|
||||
{
|
||||
"AccountTag": "${BOOTSTRAP_CLOUDFLARE_ACCOUNT_TAG}",
|
||||
"TunnelSecret": "${BOOTSTRAP_CLOUDFLARE_TUNNEL_SECRET}",
|
||||
"TunnelID": "${BOOTSTRAP_CLOUDFLARE_TUNNEL_ID}"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: cluster-secrets
|
||||
namespace: flux-system
|
||||
stringData:
|
||||
SECRET_DOMAIN: "${BOOTSTRAP_CLOUDFLARE_DOMAIN}"
|
||||
SECRET_ACME_EMAIL: "${BOOTSTRAP_ACME_EMAIL}"
|
||||
SECRET_CLOUDFLARE_TUNNEL_ID: "${BOOTSTRAP_CLOUDFLARE_TUNNEL_ID}"
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cluster-settings
|
||||
namespace: flux-system
|
||||
data:
|
||||
TIMEZONE: "${BOOTSTRAP_TIMEZONE}"
|
||||
K8S_GATEWAY_ADDR: "${BOOTSTRAP_K8S_GATEWAY_ADDR}"
|
||||
INGRESS_NGINX_ADDR: "${BOOTSTRAP_INGRESS_NGINX_ADDR}"
|
||||
KUBE_VIP_ADDR: "${BOOTSTRAP_KUBE_VIP_ADDR}"
|
||||
CLUSTER_CIDR: "${BOOTSTRAP_CLUSTER_CIDR}"
|
||||
SERVICE_CIDR: "${BOOTSTRAP_SERVICE_CIDR}"
|
||||
Reference in new issue
Block a user