From 571a7454c0818aff912d387ca97193f1c4aa539d Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Wed, 19 Jul 2023 11:03:59 -0400 Subject: [PATCH] refactor: install ansible with Python venv (#863) --- .envrc | 11 ++- .github/renovate.json5 | 11 --- .gitignore | 3 +- .taskfiles/AnsibleTasks.yaml | 71 ++++--------------- .taskfiles/BrewTasks.yaml | 4 -- .taskfiles/ClusterTasks.yaml | 2 +- README.md | 65 ++++++++--------- Taskfile.yaml | 16 ++++- bootstrap/tasks/validation/age.yaml | 8 +-- bootstrap/tasks/validation/cli.yaml | 2 +- bootstrap/templates/ansible/ansible.cfg.j2 | 35 --------- .../playbooks/cluster-rollout-update.yaml.j2 | 2 +- .../templates/ansible/requirements.txt.j2 | 1 - requirements.txt | 6 ++ .../requirements.yaml.j2 => requirements.yaml | 1 - 15 files changed, 85 insertions(+), 153 deletions(-) delete mode 100644 bootstrap/templates/ansible/ansible.cfg.j2 delete mode 100644 bootstrap/templates/ansible/requirements.txt.j2 create mode 100644 requirements.txt rename bootstrap/templates/ansible/requirements.yaml.j2 => requirements.yaml (83%) diff --git a/.envrc b/.envrc index e146bb1d..b5ff8e08 100644 --- a/.envrc +++ b/.envrc @@ -1,4 +1,9 @@ #shellcheck disable=SC2148,SC2155 -export KUBECONFIG=$(expand_path ./kubeconfig) -export K8S_AUTH_KUBECONFIG=$(expand_path ./kubeconfig) -export SOPS_AGE_KEY_FILE=$(expand_path ~/.config/sops/age/keys.txt) +export KUBECONFIG="$(expand_path ./kubeconfig)" +export SOPS_AGE_KEY_FILE="$(expand_path ./age.key)" +# ansible +PATH_add "$(expand_path ./.venv/bin)" +export VIRTUAL_ENV="$(expand_path ./.venv)" +export ANSIBLE_COLLECTIONS_PATH=$(expand_path ./.venv/galaxy) +export ANSIBLE_ROLES_PATH=$(expand_path ./.venv/galaxy/ansible_roles) +export K8S_AUTH_KUBECONFIG="$(expand_path ./kubeconfig)" diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 89c39de3..869eeb16 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -20,12 +20,6 @@ "suppressNotifications": ["prIgnoreNotification"], "rebaseWhen": "conflicted", "schedule": ["on saturday"], - "ansible-galaxy": { - "fileMatch": [ - "(^|/)requirements\\.ya?ml(\\.j2)?$", - "(^|/)galaxy\\.ya?ml(\\.j2)?$" - ] - }, "flux": { "fileMatch": [ "(^|/)kubernetes/.+\\.ya?ml(\\.j2)?$", @@ -50,11 +44,6 @@ "(^|/)kustomization\\.ya?ml(\\.j2)?$" ] }, - "pip_requirements": { - "fileMatch": [ - "(^|/)[\\w-]*requirements(-\\w+)?\\.(txt|pip)(\\.j2)?$" - ] - }, "regexManagers": [ { "description": "Process various other dependencies", diff --git a/.gitignore b/.gitignore index bb0e5a98..3967685d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,10 @@ Thumbs.db # k8s kubeconfig -# vscode-sops .decrypted~*.yaml .config.env *.agekey *.pub *.key # Ansible -xanmanning.k3s* +.venv* diff --git a/.taskfiles/AnsibleTasks.yaml b/.taskfiles/AnsibleTasks.yaml index b0f174a3..66f63c85 100644 --- a/.taskfiles/AnsibleTasks.yaml +++ b/.taskfiles/AnsibleTasks.yaml @@ -1,95 +1,54 @@ --- version: "3" -env: - K8S_AUTH_KUBECONFIG: "{{.ROOT_DIR}}/kubeconfig" - vars: ANSIBLE_PLAYBOOK_DIR: "{{.ANSIBLE_DIR}}/playbooks" ANSIBLE_INVENTORY_DIR: "{{.ANSIBLE_DIR}}/inventory" tasks: - deps: - 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 - preconditions: - - test -f "{{.ANSIBLE_DIR}}/requirements.txt" - - test -f "{{.ANSIBLE_DIR}}/requirements.yaml" - env: - ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg" - - list: - desc: List all the hosts - dir: "{{.ANSIBLE_DIR}}" - cmd: ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml --list-hosts - env: - ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg" - prepare: desc: Prepare all the k8s nodes for running k3s dir: "{{.ANSIBLE_DIR}}" cmd: 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}}" cmd: ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-installation.yaml - env: - ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg" rollout-update: desc: Preform operating system updates and rollout restart the cluster dir: "{{.ANSIBLE_DIR}}" cmd: ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-rollout-update.yaml - env: - ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg" - - kube-vip: - desc: Apply new kube-vip configuration to the cluster - dir: "{{.ANSIBLE_DIR}}" - cmd: ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-kube-vip.yaml - env: - ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg" nuke: desc: Uninstall Kubernetes on the nodes dir: "{{.ANSIBLE_DIR}}" interactive: true cmd: ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-nuke.yaml - env: - ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg" + + reboot: + desc: Reboot all the k8s nodes + dir: "{{.ANSIBLE_DIR}}" + cmd: ansible-playbook -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml {{.ANSIBLE_PLAYBOOK_DIR}}/cluster-reboot.yaml + + poweroff: + desc: Shutdown all the k8s nodes + dir: "{{.ANSIBLE_DIR}}" + cmd: ansible kubernetes -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml -a '/usr/bin/systemctl poweroff' --become + + list: + desc: List all the hosts + dir: "{{.ANSIBLE_DIR}}" + cmd: ansible all -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml --list-hosts ping: desc: Ping all the hosts dir: "{{.ANSIBLE_DIR}}" cmd: 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}}" cmd: 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}}" - cmd: 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}}" - cmd: ansible kubernetes -i {{.ANSIBLE_INVENTORY_DIR}}/hosts.yaml -a '/usr/bin/systemctl poweroff' --become - env: - ANSIBLE_CONFIG: "{{.ANSIBLE_DIR}}/ansible.cfg" diff --git a/.taskfiles/BrewTasks.yaml b/.taskfiles/BrewTasks.yaml index e7b0bc14..fc8c4b1f 100644 --- a/.taskfiles/BrewTasks.yaml +++ b/.taskfiles/BrewTasks.yaml @@ -14,16 +14,12 @@ tasks: vars: DEPS: >- age - ansible cilium-cli cloudflared - direnv fluxcd/tap/flux - go-task/tap/go-task helm kubernetes-cli kustomize - python sops stern yq diff --git a/.taskfiles/ClusterTasks.yaml b/.taskfiles/ClusterTasks.yaml index 197113c8..6d1b6129 100644 --- a/.taskfiles/ClusterTasks.yaml +++ b/.taskfiles/ClusterTasks.yaml @@ -20,7 +20,7 @@ tasks: msg: | Age key file is not found. Did you forget to create it? vars: - SOPS_AGE_KEY_FILE: ~/.config/sops/age/keys.txt + SOPS_AGE_KEY_FILE: "{{.ROOT_DIR}}/age.key" reconcile: desc: Force update Flux to pull in changes from your Git repository diff --git a/README.md b/README.md index a3183e21..9d8c6709 100644 --- a/README.md +++ b/README.md @@ -99,33 +99,47 @@ According to the documentation [here](https://raspi.debian.net/defaults-and-sett The very first step will be to create a new **public** repository by clicking the big green **Use this template** button on this page. Next clone **your new repo** to you local workstation and `cd` into it. -📍 _**All of the below commands** are run on your **local** workstation, **not** on any of your cluster nodes._ +📍 _**All commands** are run on your **local** workstation within your repository directory_ ## 🔧 Workstation Tools -📍 _Install the **most recent version** of the CLI tools below. If you are **having trouble with future steps**, it is very likely you **don't have the most recent version** of these CLI tools. The most troublesome are `ansible`, `go-task`, and `sops`._ +Lets get the required workstation tools installed and configured. -1. Install the following CLI tools on your workstation, if you are using [Homebrew](https://brew.sh/) skip this step and move onto 2 & 3. +1. Install the most recent version of [task](https://taskfile.dev/) - - **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), [kubectl](https://kubernetes.io/docs/tasks/tools/), [python3](https://www.python.org/), [python-pip3](https://pypi.org/project/pip/), [sops](https://github.com/getsops/sops) - - - **Recommended**: [helm](https://helm.sh/), [kustomize](https://github.com/kubernetes-sigs/kustomize), [stern](https://github.com/stern/stern) - -2. [Homebrew] Install [go-task](https://github.com/go-task/task) + 📍 _See the task [installation docs](https://taskfile.dev/installation/) for other platforms_ ```sh - brew install go-task/tap/go-task + # Brew + brew install go-task ``` -3. [Homebrew] Install the other workstation dependencies +2. Install the most recent version of [direnv](https://direnv.net/) + + 📍 _See the direnv [installation docs](https://direnv.net/docs/installation.html) for other platforms_ + + 📍 _After installing `direnv` be sure to [hook it into your shell](https://direnv.net/docs/hook.html) and after that is done run `direnv allow` while in your repos directory._ ```sh - task brew:deps + # Brew + brew install direnv ``` -## 🌱 Environment +3. Setup a Python virual env and install Ansible by running the following task command. -Next take a moment and configure [direnv](https://direnv.net/). This tool will make it so anytime you `cd` to your repo's directory it export the required environment variables (e.g. `KUBECONFIG`). To set this up make sure you [hook it into your shell](https://direnv.net/docs/hook.html) and after that is done, run `direnv allow` while in your repos directory. + 📍 _This commands requires Python 3.8+ to be installed_ + + ```sh + # Platform agnostic + task deps + ``` + +4. Install the required tools: [age](https://github.com/FiloSottile/age), [flux](https://toolkit.fluxcd.io/), [cloudflared](https://github.com/cloudflare/cloudflared), [kubectl](https://kubernetes.io/docs/tasks/tools/), [sops](https://github.com/getsops/sops) + + ```sh + # Brew + task brew:deps + ``` ## 📄 Configuration @@ -141,20 +155,13 @@ Next take a moment and configure [direnv](https://direnv.net/). This tool will m 📍 _Using [SOPS](https://github.com/getsops/sops) with [Age](https://github.com/FiloSottile/age) allows us to encrypt secrets and use them in Ansible and Flux._ - 2a. Create a Age private / public key + 2a. Create a Age private / public key (this file is gitignored) ```sh - age-keygen -o age.agekey + age-keygen -o age.key ``` - 2b. Create the directory for the Age key and move the Age file to it - - ```sh - mkdir -p ~/.config/sops/age - mv age.agekey ~/.config/sops/age/keys.txt - ``` - - 2c. Fill out the appropriate vars in `bootstrap/vars/config.yaml` + 2b. Fill out the appropriate vars in `bootstrap/vars/config.yaml` 3. Create Cloudflare API Token @@ -224,25 +231,19 @@ Next take a moment and configure [direnv](https://direnv.net/). This tool will m 1. Ensure you are able to SSH into your nodes from your workstation using a private SSH key **without a passphrase**. For example using a SSH agent. This is how Ansible is able to connect to your remote nodes. -2. Install the Ansible deps - - ```sh - task ansible:deps - ``` - -3. Verify Ansible can view your config +2. Verify Ansible can view your config ```sh task ansible:list ``` -4. Verify Ansible can ping your nodes +3. Verify Ansible can ping your nodes ```sh task ansible:ping ``` -5. Run the Ansible prepare playbook (nodes wil reboot when done) +4. Run the Ansible prepare playbook (nodes wil reboot when done) ```sh task ansible:prepare diff --git a/Taskfile.yaml b/Taskfile.yaml index c01950c5..491b3b38 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -2,13 +2,19 @@ version: "3" vars: + PYTHON_BIN: python3 BOOTSTRAP_DIR: "{{.ROOT_DIR}}/bootstrap" ANSIBLE_DIR: "{{.ROOT_DIR}}/ansible" KUBERNETES_DIR: "{{.ROOT_DIR}}/kubernetes" env: KUBECONFIG: "{{.ROOT_DIR}}/kubeconfig" - SOPS_AGE_KEY_FILE: ~/.config/sops/age/keys.txt + SOPS_AGE_KEY_FILE: "{{.ROOT_DIR}}/age.key" + PATH: "{{.ROOT_DIR}}/.venv/bin:$PATH" + VIRTUAL_ENV: "{{.ROOT_DIR}}/.venv" + ANSIBLE_COLLECTIONS_PATH: "{{.ROOT_DIR}}/.venv/galaxy" + ANSIBLE_ROLES_PATH: "{{.ROOT_DIR}}/.venv/galaxy/ansible_roles" + K8S_AUTH_KUBECONFIG: "{{.ROOT_DIR}}/kubeconfig" includes: ansible: .taskfiles/AnsibleTasks.yaml @@ -19,6 +25,14 @@ tasks: default: task -l + deps: + desc: Create a Python virtual env and install required packages + cmds: + - "{{.PYTHON_BIN}} -m venv {{.ROOT_DIR}}/.venv" + - .venv/bin/python3 -m pip install --upgrade pip setuptools wheel + - .venv/bin/python3 -m pip install --upgrade --requirement "{{.ROOT_DIR}}/requirements.txt" + - .venv/bin/ansible-galaxy install --role-file "{{.ROOT_DIR}}/requirements.yaml" --force + init: desc: Initialize configuration files dir: "{{.BOOTSTRAP_DIR}}" diff --git a/bootstrap/tasks/validation/age.yaml b/bootstrap/tasks/validation/age.yaml index 6cc52f61..81d6ae8e 100644 --- a/bootstrap/tasks/validation/age.yaml +++ b/bootstrap/tasks/validation/age.yaml @@ -1,18 +1,18 @@ --- - name: Query age key file ansible.builtin.stat: - path: ~/.config/sops/age/keys.txt + path: "{{ repository_path }}/age.key" 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 + success_msg: Age file {{ repository_path }}/age.key exists + fail_msg: Age file {{ repository_path }}/age.key does not exist - name: Query age key file contents ansible.builtin.set_fact: - age_contents: "{{ lookup('file', '~/.config/sops/age/keys.txt') }}" + age_contents: "{{ lookup('file', repository_path + '/age.key') }}" - name: Check if age public keys match ansible.builtin.assert: diff --git a/bootstrap/tasks/validation/cli.yaml b/bootstrap/tasks/validation/cli.yaml index e1865088..7e0215b9 100644 --- a/bootstrap/tasks/validation/cli.yaml +++ b/bootstrap/tasks/validation/cli.yaml @@ -2,7 +2,7 @@ - name: Check for required CLI tools ansible.builtin.shell: | command -v {{ item }} >/dev/null 2>&1 - loop: [age, cloudflared, flux, git, pip3, sops, task] + loop: [age, cloudflared, flux, sops] changed_when: false check_mode: false register: result diff --git a/bootstrap/templates/ansible/ansible.cfg.j2 b/bootstrap/templates/ansible/ansible.cfg.j2 deleted file mode 100644 index 021bd030..00000000 --- a/bootstrap/templates/ansible/ansible.cfg.j2 +++ /dev/null @@ -1,35 +0,0 @@ -[defaults] -# General settings -nocows = True -executable = /bin/bash -stdout_callback = yaml -force_valid_group_names = ignore -# File/Directory settings -log_path = ~/.ansible/ansible.log -inventory = ./inventory -roles_path = ~/.ansible/roles:./roles -collections_path = ~/.ansible/collections -remote_tmp = ~/.ansible/tmp -local_tmp = ~/.ansible/tmp -# Fact Caching settings -fact_caching = jsonfile -fact_caching_connection = ~/.ansible/facts_cache -# SSH settings -remote_port = 22 -timeout = 60 -host_key_checking = False -# Plugin settings -vars_plugins_enabled = host_group_vars,community.sops.sops - -[inventory] -unparsed_is_failed = True - -[privilege_escalation] -become = True - -[ssh_connection] -scp_if_ssh = smart -retries = 3 -ssh_args = -o ControlMaster=auto -o ControlPersist=30m -o Compression=yes -o ServerAliveInterval=15s -pipelining = True -control_path = %(directory)s/%%h-%%r diff --git a/bootstrap/templates/ansible/playbooks/cluster-rollout-update.yaml.j2 b/bootstrap/templates/ansible/playbooks/cluster-rollout-update.yaml.j2 index ca3a7e43..4afd9874 100644 --- a/bootstrap/templates/ansible/playbooks/cluster-rollout-update.yaml.j2 +++ b/bootstrap/templates/ansible/playbooks/cluster-rollout-update.yaml.j2 @@ -41,7 +41,7 @@ - name: Drain ansible.builtin.command: "kubectl drain --ignore-daemonsets --delete-emptydir-data --force {% raw %}{{ inventory_hostname }}{% endraw %}" - delegate_to: "{{ groups['master'][0] }}" + delegate_to: "{% raw %}{{ groups['master'][0] }}{% endraw %}" changed_when: false - name: Update diff --git a/bootstrap/templates/ansible/requirements.txt.j2 b/bootstrap/templates/ansible/requirements.txt.j2 deleted file mode 100644 index db89003e..00000000 --- a/bootstrap/templates/ansible/requirements.txt.j2 +++ /dev/null @@ -1 +0,0 @@ -openshift==0.13.1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..b5401708 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +ansible==8.2.0 +ansible-lint==6.17.2 +bcrypt==4.0.1 +netaddr==0.8.0 +openshift==0.13.1 +passlib==1.7.4 diff --git a/bootstrap/templates/ansible/requirements.yaml.j2 b/requirements.yaml similarity index 83% rename from bootstrap/templates/ansible/requirements.yaml.j2 rename to requirements.yaml index ec9b301b..4d56b1bb 100644 --- a/bootstrap/templates/ansible/requirements.yaml.j2 +++ b/requirements.yaml @@ -12,5 +12,4 @@ collections: version: 2.4.0 roles: - name: xanmanning.k3s - src: https://github.com/PyratLabs/ansible-role-k3s.git version: v3.4.2