From 542d3af8da9de2cf14909ff4ac1a63f038431ca6 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Thu, 28 Mar 2024 18:29:07 +0100 Subject: [PATCH] Init commit --- .containerignore | 2 + .gitignore | 3 +- .sops.yaml | 5 + Containerfile | 27 ++++++ Makefile | 37 ++++++++ README.md | 15 +++ bin/ssh-to-every-host | 3 + group_vars/hetzner.sops.yaml | 21 ++++ group_vars/packages.yaml | 13 +++ group_vars/sysctl.yaml | 13 +++ group_vars/users.yaml | 11 +++ playbooks/other/k8s-create-user/.gitignore | 6 ++ playbooks/other/k8s-create-user/Makefile | 34 +++++++ playbooks/other/k8s-create-user/README.md | 15 +++ playbooks/other/k8s-create-user/ansible.cfg | 2 + playbooks/other/k8s-create-user/playbook.yml | 11 +++ playbooks/other/ssh-key-gen/.gitignore | 5 + playbooks/other/ssh-key-gen/.sops.yaml | 5 + playbooks/other/ssh-key-gen/LICENSE | 17 ++++ playbooks/other/ssh-key-gen/Makefile | 38 ++++++++ playbooks/other/ssh-key-gen/README.md | 22 +++++ playbooks/other/ssh-key-gen/playbook.yml | 20 ++++ playbooks/providers/hetzner/LICENSE | 17 ++++ playbooks/providers/hetzner/Makefile | 41 ++++++++ playbooks/providers/hetzner/README.md | 22 +++++ playbooks/providers/hetzner/playbook.yml | 88 +++++++++++++++++ .../hetzner/templates/inventory.yaml.j2 | 46 +++++++++ .../templates/provider_outputs.yaml.j2 | 1 + playbooks/systems/k3s-bootstrap/.gitignore | 5 + playbooks/systems/k3s-bootstrap/LICENSE | 17 ++++ playbooks/systems/k3s-bootstrap/Makefile | 33 +++++++ playbooks/systems/k3s-bootstrap/README.md | 22 +++++ playbooks/systems/k3s-bootstrap/ansible.cfg | 2 + playbooks/systems/k3s-bootstrap/playbook.yml | 22 +++++ .../systems/k3s-bootstrap/tasks/netplan.yaml | 32 +++++++ .../systems/k3s-bootstrap/tasks/packages.yaml | 79 +++++++++++++++ .../systems/k3s-bootstrap/tasks/sysctl.yaml | 20 ++++ .../systems/k3s-bootstrap/tasks/users.yaml | 33 +++++++ .../systems/k3s-bootstrap/tasks/volumes.yaml | 33 +++++++ .../k3s-bootstrap/templates/60-floating-ip.j2 | 8 ++ .../k3s-bootstrap/templates/ifcfg-eth0:1.j2 | 7 ++ playbooks/systems/system-bootstrap/.gitignore | 5 + playbooks/systems/system-bootstrap/LICENSE | 17 ++++ playbooks/systems/system-bootstrap/Makefile | 23 +++++ playbooks/systems/system-bootstrap/README.md | 22 +++++ .../systems/system-bootstrap/ansible.cfg | 2 + .../systems/system-bootstrap/playbook.yml | 25 +++++ .../system-bootstrap/tasks/netplan.yaml | 32 +++++++ .../system-bootstrap/tasks/packages.yaml | 95 +++++++++++++++++++ .../system-bootstrap/tasks/sysctl.yaml | 20 ++++ .../systems/system-bootstrap/tasks/users.yaml | 33 +++++++ .../system-bootstrap/tasks/volumes.yaml | 33 +++++++ .../templates/60-floating-ip.j2 | 8 ++ .../system-bootstrap/templates/docker.repo.j2 | 6 ++ .../templates/ifcfg-eth0:1.j2 | 7 ++ .../templates/kubernetes.repo.j2 | 6 ++ requirements.txt | 2 + requirements.yml | 15 +++ resources/known_hosts | 2 + 59 files changed, 1205 insertions(+), 1 deletion(-) create mode 100644 .containerignore create mode 100644 .sops.yaml create mode 100644 Containerfile create mode 100644 Makefile create mode 100755 bin/ssh-to-every-host create mode 100644 group_vars/hetzner.sops.yaml create mode 100644 group_vars/packages.yaml create mode 100644 group_vars/sysctl.yaml create mode 100644 group_vars/users.yaml create mode 100644 playbooks/other/k8s-create-user/.gitignore create mode 100644 playbooks/other/k8s-create-user/Makefile create mode 100644 playbooks/other/k8s-create-user/README.md create mode 100644 playbooks/other/k8s-create-user/ansible.cfg create mode 100644 playbooks/other/k8s-create-user/playbook.yml create mode 100644 playbooks/other/ssh-key-gen/.gitignore create mode 100644 playbooks/other/ssh-key-gen/.sops.yaml create mode 100644 playbooks/other/ssh-key-gen/LICENSE create mode 100644 playbooks/other/ssh-key-gen/Makefile create mode 100644 playbooks/other/ssh-key-gen/README.md create mode 100644 playbooks/other/ssh-key-gen/playbook.yml create mode 100644 playbooks/providers/hetzner/LICENSE create mode 100644 playbooks/providers/hetzner/Makefile create mode 100644 playbooks/providers/hetzner/README.md create mode 100644 playbooks/providers/hetzner/playbook.yml create mode 100644 playbooks/providers/hetzner/templates/inventory.yaml.j2 create mode 100644 playbooks/providers/hetzner/templates/provider_outputs.yaml.j2 create mode 100644 playbooks/systems/k3s-bootstrap/.gitignore create mode 100644 playbooks/systems/k3s-bootstrap/LICENSE create mode 100644 playbooks/systems/k3s-bootstrap/Makefile create mode 100644 playbooks/systems/k3s-bootstrap/README.md create mode 100644 playbooks/systems/k3s-bootstrap/ansible.cfg create mode 100644 playbooks/systems/k3s-bootstrap/playbook.yml create mode 100644 playbooks/systems/k3s-bootstrap/tasks/netplan.yaml create mode 100644 playbooks/systems/k3s-bootstrap/tasks/packages.yaml create mode 100644 playbooks/systems/k3s-bootstrap/tasks/sysctl.yaml create mode 100644 playbooks/systems/k3s-bootstrap/tasks/users.yaml create mode 100644 playbooks/systems/k3s-bootstrap/tasks/volumes.yaml create mode 100644 playbooks/systems/k3s-bootstrap/templates/60-floating-ip.j2 create mode 100644 playbooks/systems/k3s-bootstrap/templates/ifcfg-eth0:1.j2 create mode 100644 playbooks/systems/system-bootstrap/.gitignore create mode 100644 playbooks/systems/system-bootstrap/LICENSE create mode 100644 playbooks/systems/system-bootstrap/Makefile create mode 100644 playbooks/systems/system-bootstrap/README.md create mode 100644 playbooks/systems/system-bootstrap/ansible.cfg create mode 100644 playbooks/systems/system-bootstrap/playbook.yml create mode 100644 playbooks/systems/system-bootstrap/tasks/netplan.yaml create mode 100644 playbooks/systems/system-bootstrap/tasks/packages.yaml create mode 100644 playbooks/systems/system-bootstrap/tasks/sysctl.yaml create mode 100644 playbooks/systems/system-bootstrap/tasks/users.yaml create mode 100644 playbooks/systems/system-bootstrap/tasks/volumes.yaml create mode 100644 playbooks/systems/system-bootstrap/templates/60-floating-ip.j2 create mode 100644 playbooks/systems/system-bootstrap/templates/docker.repo.j2 create mode 100644 playbooks/systems/system-bootstrap/templates/ifcfg-eth0:1.j2 create mode 100644 playbooks/systems/system-bootstrap/templates/kubernetes.repo.j2 create mode 100644 requirements.txt create mode 100644 requirements.yml create mode 100644 resources/known_hosts diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000..fdc5bd4 --- /dev/null +++ b/.containerignore @@ -0,0 +1,2 @@ +venv +.ansible diff --git a/.gitignore b/.gitignore index 5c199eb..9dca8ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # ---> Ansible *.retry - +.ansible +venv diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..9174846 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,5 @@ +creation_rules: + - path_regex: .* + key_groups: + - age: + - age1lzythn62c4yug8w2wskckpgyjyja6rreyvgmwl9hj4mjvm0tvq6sl68d4z diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..47825be --- /dev/null +++ b/Containerfile @@ -0,0 +1,27 @@ +FROM python:3.12.2-slim-bullseye AS compile-image +RUN apt-get update +RUN apt-get install -y --no-install-recommends build-essential gcc libffi-dev git vim ssh +RUN mkdir ~/.ssh +COPY ./resources/known_hosts ~/.ssh/known_hosts +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +COPY requirements.yml . +RUN pip install hcloud ansible +RUN ansible-galaxy install -r ./requirements.yml --force + +FROM ghcr.io/allanger/dumb-downloader as dudo +ARG SOPS_VERSION=v3.8.1 +ENV RUST_LOG=info +RUN mkdir -p /out +RUN dudo -l "https://github.com/getsops/sops/releases/download/{{ version }}/sops-{{ version }}.{{ os }}.{{ arch }}" -d /out/sops -p $SOPS_VERSION +RUN chmod +x /out/sops + +FROM python:3.12.2-slim-bullseye AS build-image +COPY --from=compile-image /opt/venv /opt/venv +RUN apt-get update && \ + apt-get install age -y +COPY . /src +COPY --from=dudo /out/sops /usr/bin/sops +ENV PATH="/opt/venv/bin:$PATH" +CMD ['ansible'] + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ce9ee05 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +CUSTOMER ?= softplayer +ENV ?= default + +venv-create: + python3 -m venv venv + source venv/bin/activate && \ + export ANSIBLE_COLLECTIONS_PATH=./.ansible/collections && \ + export ANSIBLE_ROLES_PATH=./.ansible/roles && \ + python3 -m pip install -r ./requirements.txt && \ + python3 -m ansible galaxy install -r ./requirements.yml --force + +bootstrap: + source venv/bin/activate && \ + export ANSIBLE_HOST_KEY_CHECKING=false && \ + export ANSIBLE_COLLECTIONS_PATH=./.ansible/collections && \ + export ANSIBLE_ROLES_PATH=./.ansible/roles && \ + export SP_CUSTOMER=softplayer && \ + export SP_ENV=dev && \ + export SP_STATE=present && \ + python3 -m ansible playbook ./playbooks/other/ssh-key-gen/playbook.yml && \ + python3 -m ansible playbook ./playbooks/providers/hetzner/playbook.yml && \ + export ANSIBLE_INVENTORY=/tmp/outputs/inventory.yaml && \ + export ANSIBE_PRIVATE_KEY_FILE=/tmp/outputs/ssh_key && \ + sleep 8 && \ + python3 -m ansible playbook ./playbooks/systems/system-bootstrap/playbook.yml && \ + python3 -m ansible playbook ./playbooks/systems/k3s-bootstrap/playbook.yml && \ + python3 -m ansible playbook ./playbooks/other/k8s-create-user/playbook.yml + +cleanup: + source venv/bin/activate && \ + export ANSIBLE_HOST_KEY_CHECKING=false && \ + export COLLECTIONS_PATHS=./.ansible/collections && \ + export ANSIBLE_ROLES_PATH=./.ansible/roles && \ + export SP_CUSTOMER=softplayer && \ + export SP_ENV=dev && \ + export SP_STATE=absent && \ + python3 -m ansible playbook ./playbooks/providers/hetzner/playbook.yml diff --git a/README.md b/README.md index 377f21f..ff286ff 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,17 @@ # softplayer-ansible +## Hetzner + +There must be 3 kinds of playbooks: + - Bootstrap + - Maintenance + - Cleanup + +Bootstrap playbooks should only be executed when a new environment is created, for example, it should + +1. Prepare the cloud infrastructure +2. Install setup nodes +3. Bootstrap a k3s cluster +4. Create an admin k8s config + +Cleanup should simply remove the whole cloud infrastructure diff --git a/bin/ssh-to-every-host b/bin/ssh-to-every-host new file mode 100755 index 0000000..3f18a8a --- /dev/null +++ b/bin/ssh-to-every-host @@ -0,0 +1,3 @@ +#/usr/bin/env bash + +for i in $(yq '.kubernetes.hosts | keys' inventory.yaml -o tsv); do ssh $i; done \ No newline at end of file diff --git a/group_vars/hetzner.sops.yaml b/group_vars/hetzner.sops.yaml new file mode 100644 index 0000000..57a0d29 --- /dev/null +++ b/group_vars/hetzner.sops.yaml @@ -0,0 +1,21 @@ +api_token: ENC[AES256_GCM,data:espaqpkBv6yn/Jsz6Eiv/xp0NJIiv871AioqeXbZ4epBk9FUsBLSDEIbbak7hN8EL3wLkLDVNZmPF1G8b1UQBg==,iv:CWL6uX1lRz1hisYYWu5K/btWJyIn4OfNpBuylHaY5UI=,tag:GWcqBqNjrwLDL7RfChvM+g==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1lzythn62c4yug8w2wskckpgyjyja6rreyvgmwl9hj4mjvm0tvq6sl68d4z + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpcDNMeGtWeC9PdUFzMmlj + MUxFekZXVkVtREVSVFVnTFJoQzBJN05Bb2c0CnVaUmEwWlk0Z21KbHp5QmxBQnlB + aUxGUjJXNXVXRXMrVFArMDRXOVJFRmsKLS0tICt2SlFtT1o2QTFVYkZoeHpFU3d4 + U0FzT1h2V1ZUSVoxcndVL2tlYnNpZDQKrLmIImEyY/2DNsX8xzthSNRL9/BGxB82 + eksaJS+98LD7byPoxtBlbqqbxvnj5qr8D7G3wWe0eIfHzj/a1MtKLQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2024-03-12T13:34:49Z" + mac: ENC[AES256_GCM,data:t3OtDE8hH4hiqfE0CemCm0rQ4psLeMnMcqFtz1qki5gybmAxxFIiF+Y/9NuRZk2h8YPLyLAafWVfUBQ2vrcL4BkfexrSS3ouomibbfK94C5FZ7XIhl0K0grHKQa8oTvL3ErACPbnsKCa59WhGxmyJPi3MbZ9cWnMNRiv/BdYb6Y=,iv:o0cGK/WSwaA7EfkxofJEez3fwt0kkGhJrZR5Ls2sJAs=,tag:I/LFakV5N6SQqdXY7Cuauw==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.8.1 diff --git a/group_vars/packages.yaml b/group_vars/packages.yaml new file mode 100644 index 0000000..42f93cf --- /dev/null +++ b/group_vars/packages.yaml @@ -0,0 +1,13 @@ +common_packages: + - name: python3-cryptography + state: present + - name: iscsi-initiator-utils + state: present + - name: nfs-utils + state: present + - name: cifs-utils + state: present +common_services: + - name: iscsid + state: started + enabled: true diff --git a/group_vars/sysctl.yaml b/group_vars/sysctl.yaml new file mode 100644 index 0000000..7fad0d6 --- /dev/null +++ b/group_vars/sysctl.yaml @@ -0,0 +1,13 @@ +common_sysctl: + - name: fs.inotify.max_user_watches + value: 2099999999 + set: true + state: present + - name: fs.inotify.max_user_instances + value: 2099999999 + set: true + state: present + - name: fs.inotify.max_queued_events + value: 2099999999 + set: true + state: present diff --git a/group_vars/users.yaml b/group_vars/users.yaml new file mode 100644 index 0000000..e3194ee --- /dev/null +++ b/group_vars/users.yaml @@ -0,0 +1,11 @@ +user_groups: + - name: wheel + state: present + sudo: true +users: + - name: admin + groups: + - wheel + ssh_keys: + - key: "{{ lookup('file', '/tmp/outputs/ssh_key.pub') }}" + state: present diff --git a/playbooks/other/k8s-create-user/.gitignore b/playbooks/other/k8s-create-user/.gitignore new file mode 100644 index 0000000..5443eb5 --- /dev/null +++ b/playbooks/other/k8s-create-user/.gitignore @@ -0,0 +1,6 @@ +# ---> Ansible +*.retry +venv +.ansible + +config diff --git a/playbooks/other/k8s-create-user/Makefile b/playbooks/other/k8s-create-user/Makefile new file mode 100644 index 0000000..fc4e707 --- /dev/null +++ b/playbooks/other/k8s-create-user/Makefile @@ -0,0 +1,34 @@ +venv: venv-create venv-install-deps venv-install-reqs +run: venv venv-run +check: venv venv-check + +venv-create: + python3 -m venv venv + +venv-install-deps: + source venv/bin/activate &&\ + python3 -m pip install hcloud ansible ansible-lint + +venv-install-reqs: + source venv/bin/activate &&\ + python3 -m ansible galaxy role install -r ./requirements.yml -p ./.ansible/roles + +venv-install-reqs-force: + source venv/bin/activate &&\ + python3 -m ansible galaxy role install -r ./requirements.yml --force -p ./.ansible/roles + +venv-check: + source venv/bin/activate &&\ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ROLES_PATH=./.ansible/roles &&\ + python3 -m ansible playbook playbook.yml --check -i ./inventory.yaml + +venv-run: + source venv/bin/activate &&\ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ANSIBLE_ROLES_PATH=$(PWD)/.ansible/roles &&\ + python3 -m ansible playbook playbook.yml -i /tmp/outputs/inventory.yaml + +venv-lint: + venv/bin/ansible-lint playbook.yml + diff --git a/playbooks/other/k8s-create-user/README.md b/playbooks/other/k8s-create-user/README.md new file mode 100644 index 0000000..5cfe6a9 --- /dev/null +++ b/playbooks/other/k8s-create-user/README.md @@ -0,0 +1,15 @@ +# k8s-ansible-playbooks + + +## Get a config + +To get a k8s config, just run the script: +```bash +./bin/get_configs $USERNAME +``` + +It will create a config file in the repo root dir + +```bash +cat config +``` diff --git a/playbooks/other/k8s-create-user/ansible.cfg b/playbooks/other/k8s-create-user/ansible.cfg new file mode 100644 index 0000000..b959744 --- /dev/null +++ b/playbooks/other/k8s-create-user/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +vars_plugins_enabled = host_group_vars,community.sops.sops diff --git a/playbooks/other/k8s-create-user/playbook.yml b/playbooks/other/k8s-create-user/playbook.yml new file mode 100644 index 0000000..a750a9c --- /dev/null +++ b/playbooks/other/k8s-create-user/playbook.yml @@ -0,0 +1,11 @@ +- hosts: k8s + name: Create k8s users + remote_user: admin + become: true + roles: + - ansible-create-k8s-user + vars: + kubectl: + version: v1.27.4 + arch: amd64 + working_dir: /home/admin/.k8s diff --git a/playbooks/other/ssh-key-gen/.gitignore b/playbooks/other/ssh-key-gen/.gitignore new file mode 100644 index 0000000..655f652 --- /dev/null +++ b/playbooks/other/ssh-key-gen/.gitignore @@ -0,0 +1,5 @@ +# ---> Ansible +*.retry + +# ---> Python +venv diff --git a/playbooks/other/ssh-key-gen/.sops.yaml b/playbooks/other/ssh-key-gen/.sops.yaml new file mode 100644 index 0000000..9174846 --- /dev/null +++ b/playbooks/other/ssh-key-gen/.sops.yaml @@ -0,0 +1,5 @@ +creation_rules: + - path_regex: .* + key_groups: + - age: + - age1lzythn62c4yug8w2wskckpgyjyja6rreyvgmwl9hj4mjvm0tvq6sl68d4z diff --git a/playbooks/other/ssh-key-gen/LICENSE b/playbooks/other/ssh-key-gen/LICENSE new file mode 100644 index 0000000..d5075a3 --- /dev/null +++ b/playbooks/other/ssh-key-gen/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the +above copyright notice and the following two paragraphs appear in +all copies of this software. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/playbooks/other/ssh-key-gen/Makefile b/playbooks/other/ssh-key-gen/Makefile new file mode 100644 index 0000000..610162c --- /dev/null +++ b/playbooks/other/ssh-key-gen/Makefile @@ -0,0 +1,38 @@ +venv: venv-create venv-install-deps +run: venv venv-run +check: venv venv-check + + +venv-create: + python3 -m venv venv + +venv-install-deps: + source venv/bin/activate &&\ + python3 -m pip install ansible ansible-lint + +venv-check: + source venv/bin/activate &&\ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ROLES_PATH=./.ansible/roles &&\ + python3 -m ansible playbook playbook.yml --check -i ./inventory.yaml + +venv-run: + source venv/bin/activate &&\ + export SP_CUSTOMER=allanger && \ + export SP_ENV=default && \ + export SP_STATE=present && \ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ANSIBLE_ROLES_PATH=$(PWD)/.ansible/roles &&\ + python3 -m ansible playbook playbook.yml -i ./inventory.yaml + +venv-run-cleanup: + source venv/bin/activate &&\ + export SP_CUSTOMER=allanger && \ + export SP_ENV=default && \ + export SP_STATE=absent && \ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ANSIBLE_ROLES_PATH=$(PWD)/.ansible/roles &&\ + python3 -m ansible playbook playbook.yml -i ./inventory.yaml +venv-lint: + venv/bin/ansible-lint playbook.yml + diff --git a/playbooks/other/ssh-key-gen/README.md b/playbooks/other/ssh-key-gen/README.md new file mode 100644 index 0000000..5f3d34e --- /dev/null +++ b/playbooks/other/ssh-key-gen/README.md @@ -0,0 +1,22 @@ +# hetzner-ansilbe-playbook + +Repo for managing the Hetzner infrastructure + +## Removing stuff + +Since the state of the config is the ansible code itself, you can't just remove something from the code and expect that it's going to be removed from Hetzner. +Each entity has a variable `state`, to remove anything, you need to set state to `absent` and run the playbook. And only after that you can remove it from the code. + +Also, please, create a git commit, where on object with the `absent` state is tracked. + +## Outputs +After running the role you'll see three variables being logged in the last step: +- Server public IP -> It should be used for `ssh` connection to the server +- Load balancer public IP -> It should be used by k8s as the load balancer IP +- Volume device name -> It's the name of device that should be mounted to Longhorn + +## Notes + +### Resize the volume +Don't forget to resize the filesystem, it should be done manually +- https://docs.hetzner.com/cloud/volumes/faq/ diff --git a/playbooks/other/ssh-key-gen/playbook.yml b/playbooks/other/ssh-key-gen/playbook.yml new file mode 100644 index 0000000..8a4b6b0 --- /dev/null +++ b/playbooks/other/ssh-key-gen/playbook.yml @@ -0,0 +1,20 @@ +- name: Generate a SSH key + hosts: localhost + connection: local + vars: + output_dir: /tmp/outputs + tasks: + - name: Create the output directory + ansible.builtin.file: + path: "{{ output_dir }}" + state: directory + mode: '0755' + register: workdir + - name: Generate an OpenSSH keypair with the default values (4096 bits, rsa) + community.crypto.openssh_keypair: + path: "{{ output_dir}}/ssh_key" + type: ed25519 + + - name: Setting host facts using complex arguments + ansible.builtin.set_fact: + pub_key: "{{ lookup('file', '/tmp/outputs/ssh_key.pub') }}" diff --git a/playbooks/providers/hetzner/LICENSE b/playbooks/providers/hetzner/LICENSE new file mode 100644 index 0000000..d5075a3 --- /dev/null +++ b/playbooks/providers/hetzner/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the +above copyright notice and the following two paragraphs appear in +all copies of this software. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/playbooks/providers/hetzner/Makefile b/playbooks/providers/hetzner/Makefile new file mode 100644 index 0000000..5eb1894 --- /dev/null +++ b/playbooks/providers/hetzner/Makefile @@ -0,0 +1,41 @@ +venv: venv-create venv-install-deps venv-install-reqs +run: venv venv-run +check: venv venv-check + + +venv-create: + python3 -m venv venv + +venv-install-deps: + source venv/bin/activate &&\ + python3 -m pip install hcloud ansible ansible-lint + +venv-install-reqs: + source venv/bin/activate &&\ + python3 -m ansible galaxy role install -r ./requirements.yml -p ./.ansible/roles + +venv-install-reqs-force: + source venv/bin/activate &&\ + python3 -m ansible galaxy role install -r ./requirements.yml --force -p ./.ansible/roles + +venv-check: + source venv/bin/activate &&\ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ROLES_PATH=./.ansible/roles &&\ + python3 -m ansible playbook playbook.yml --check -i ./inventory.yaml + +venv-run: + source venv/bin/activate &&\ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ANSIBLE_ROLES_PATH=$(PWD)/.ansible/roles &&\ + python3 -m ansible playbook playbook.yml -i ./inventory.yaml + +venv-run-cleanup: + source venv/bin/activate &&\ + export SP_STATE=absent && \ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ANSIBLE_ROLES_PATH=$(PWD)/.ansible/roles &&\ + python3 -m ansible playbook playbook.yml -i ./inventory.yaml +venv-lint: + venv/bin/ansible-lint playbook.yml + diff --git a/playbooks/providers/hetzner/README.md b/playbooks/providers/hetzner/README.md new file mode 100644 index 0000000..5f3d34e --- /dev/null +++ b/playbooks/providers/hetzner/README.md @@ -0,0 +1,22 @@ +# hetzner-ansilbe-playbook + +Repo for managing the Hetzner infrastructure + +## Removing stuff + +Since the state of the config is the ansible code itself, you can't just remove something from the code and expect that it's going to be removed from Hetzner. +Each entity has a variable `state`, to remove anything, you need to set state to `absent` and run the playbook. And only after that you can remove it from the code. + +Also, please, create a git commit, where on object with the `absent` state is tracked. + +## Outputs +After running the role you'll see three variables being logged in the last step: +- Server public IP -> It should be used for `ssh` connection to the server +- Load balancer public IP -> It should be used by k8s as the load balancer IP +- Volume device name -> It's the name of device that should be mounted to Longhorn + +## Notes + +### Resize the volume +Don't forget to resize the filesystem, it should be done manually +- https://docs.hetzner.com/cloud/volumes/faq/ diff --git a/playbooks/providers/hetzner/playbook.yml b/playbooks/providers/hetzner/playbook.yml new file mode 100644 index 0000000..7ad1b29 --- /dev/null +++ b/playbooks/providers/hetzner/playbook.yml @@ -0,0 +1,88 @@ +- name: Hetzner playbook + hosts: localhost + connection: local + vars: + output_dir: /tmp/outputs + customer: "{{ lookup('ansible.builtin.env', 'SP_CUSTOMER') }}" + env: "{{ lookup('ansible.builtin.env', 'SP_ENV') }}" + state: "{{ lookup('ansible.builtin.env', 'SP_STATE') }}" + tags: + - cloud + tasks: + - name: Load encrypted credentials + community.sops.load_vars: + file: ../../../group_vars/hetzner.sops.yaml + # -------------------------------------------------------- + # -- Prepare SSH keys + # -------------------------------------------------------- + - name: Add SSH keys to the hetzner subscription + hetzner.hcloud.hcloud_ssh_key: + api_token: "{{ api_token }}" + name: "{{ customer }}-{{ env }}" + public_key: "{{ lookup('file', '/tmp/outputs/ssh_key.pub') }}" + state: "{{ state }}" + # -------------------------------------------------------- + # -- Prepare private networks + # -------------------------------------------------------- + - name: Prepare private networks + hetzner.hcloud.hcloud_network: + name: "{{ customer }}-{{ env }}" + ip_range: 10.0.0.0/16 + state: "{{ state }}" + api_token: "{{ api_token }}" + # -------------------------------------------------------- + # -- Prepare subnets + # -------------------------------------------------------- + - name: Prepare subnets + when: state == "present" + hetzner.hcloud.hcloud_subnetwork: + network: "{{ customer }}-{{ env }}" + ip_range: 10.0.0.0/16 + state: "{{ state }}" + network_zone: eu-central + type: cloud + api_token: "{{ api_token }}" + # -------------------------------------------------------- + # -- Create servers + # -------------------------------------------------------- + - name: Create server + hetzner.hcloud.hcloud_server: + name: "{{ customer }}-{{ env }}" + server_type: cx21 + image: rocky-9 + state: "{{ state }}" + api_token: "{{ api_token }}" + ssh_keys: + - "{{ customer }}-{{ env }}" + firewalls: + - admin + private_networks: + - "{{ customer }}-{{ env }}" + location: hel1 + register: server_data + # -------------------------------------------------------- + # -- Create floating IPs + # -------------------------------------------------------- + - name: Create a floating IP + hetzner.hcloud.hcloud_floating_ip: + name: "{{ customer }}-{{ env }}" + server: "{{ customer }}-{{ env }}" + type: ipv4 + state: "{{ state }}" + api_token: "{{ api_token }}" + register: floating_ips_data + + - name: Display important output data + block: + - name: Display info about servers public IPs + ansible.builtin.debug: + msg: + - "{{ server_data }}" + when: 'server_data' + - name: Generate new inventory + ansible.builtin.template: + src: templates/inventory.yaml.j2 + dest: "{{ output_dir }}/inventory.yaml" + vars: + ipv4_address: "{{ server_data.hcloud_server.ipv4_address }}" + cluster_name: "{{ customer }}-{{ env }}" diff --git a/playbooks/providers/hetzner/templates/inventory.yaml.j2 b/playbooks/providers/hetzner/templates/inventory.yaml.j2 new file mode 100644 index 0000000..9b41312 --- /dev/null +++ b/playbooks/providers/hetzner/templates/inventory.yaml.j2 @@ -0,0 +1,46 @@ +servers: + hosts: + {{ ipv4_address }}: + vars: + ansible_ssh_private_key_file: /tmp/outputs/ssh_key +k3s_cluster: + children: + server: + hosts: + {{ ipv4_address }}: + vars: + ansible_port: 22 + k3s_version: v1.26.9+k3s1 + token: "mytoken" # Use ansible vault if you want to keep it secret + api_endpoint: "{{ ipv4_address }}" + extra_server_args: |- + --write-kubeconfig-mode=644 \ + --node-name="k3s-1" \ + --tls-san="{{ ipv4_address }}" \ + --disable-cloud-controller \ + --disable-helm-controller \ + --disable metrics-server \ + --disable local-storage \ + --disable traefik \ + --cluster-cidr="10.244.0.0/16" \ + --flannel-backend none \ + --disable coredns + extra_agent_args: "" + ansible_ssh_private_key_file: /tmp/outputs/ssh_key +k8s: + hosts: + {{ ipv4_address }}: + name: {{ cluster_name }} + kubernetes_server: https://{{ ipv4_address }}:6443 + k8s_config_path: /etc/rancher/k3s/k3s.yaml + k8s_cert_path: /var/lib/rancher/k3s/server/tls + k8s_cert_crt_file: client-ca.crt + k8s_cert_key_file: client-ca.key + users: + - cluster: default + host_user: admin + username: admin + download_config: true + vars: + ansible_ssh_private_key_file: /tmp/outputs/ssh_key + diff --git a/playbooks/providers/hetzner/templates/provider_outputs.yaml.j2 b/playbooks/providers/hetzner/templates/provider_outputs.yaml.j2 new file mode 100644 index 0000000..42723f6 --- /dev/null +++ b/playbooks/providers/hetzner/templates/provider_outputs.yaml.j2 @@ -0,0 +1 @@ +disk_device_name: {{ disk_device_name }} diff --git a/playbooks/systems/k3s-bootstrap/.gitignore b/playbooks/systems/k3s-bootstrap/.gitignore new file mode 100644 index 0000000..655f652 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/.gitignore @@ -0,0 +1,5 @@ +# ---> Ansible +*.retry + +# ---> Python +venv diff --git a/playbooks/systems/k3s-bootstrap/LICENSE b/playbooks/systems/k3s-bootstrap/LICENSE new file mode 100644 index 0000000..d5075a3 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the +above copyright notice and the following two paragraphs appear in +all copies of this software. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/playbooks/systems/k3s-bootstrap/Makefile b/playbooks/systems/k3s-bootstrap/Makefile new file mode 100644 index 0000000..31821eb --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/Makefile @@ -0,0 +1,33 @@ +venv: venv-create venv-install-deps venv-install-reqs +run: venv venv-run +check: venv venv-check + +venv-create: + python3 -m venv venv + +venv-install-deps: + source venv/bin/activate &&\ + python3 -m pip install hcloud ansible ansible-lint + +venv-install-reqs: + source venv/bin/activate &&\ + python3 -m ansible galaxy role install -r ./requirements.yml -p ./.ansible/roles + +venv-install-reqs-force: + source venv/bin/activate &&\ + python3 -m ansible galaxy role install -r ./requirements.yml --force -p ./.ansible/roles + +venv-check: + source venv/bin/activate &&\ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ROLES_PATH=./.ansible/roles &&\ + python3 -m ansible playbook playbook.yml --check -i /tmp/outputs/inventory.yaml + +venv-run: + source venv/bin/activate &&\ + export COLLECTIONS_PATHS=./.ansible/collections &&\ + export ANSIBLE_ROLES_PATH=$(PWD)/.ansible/roles &&\ + python3 -m ansible playbook playbook.yml -i /tmp/outputs/inventory.yaml + +venv-lint: + venv/bin/ansible-lint playbook.yml diff --git a/playbooks/systems/k3s-bootstrap/README.md b/playbooks/systems/k3s-bootstrap/README.md new file mode 100644 index 0000000..1db5409 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/README.md @@ -0,0 +1,22 @@ +# systems-ansible-playbooks + +Manage servers with ansible + +## K3s + +I'm not configuring k3s with ansble, it's configured with following flags + +curl -sfL https://get.k3s.io | sh -s - server \ + --write-kubeconfig-mode=644 \ + --node-name="$(hostname -f)" \ + --kubelet-arg "allowed-unsafe-sysctls=net.ipv4.ip_forward" \ + --tls-san="$(hostname -I | awk '{print $1}')" \ + --disable-cloud-controller \ + --disable-helm-controller \ + --disable metrics-server \ + --disable local-storage \ + --disable traefik \ + --cluster-cidr="10.244.0.0/16" \ + --flannel-backend none \ + --disable coredns \ + --cluster-init \ diff --git a/playbooks/systems/k3s-bootstrap/ansible.cfg b/playbooks/systems/k3s-bootstrap/ansible.cfg new file mode 100644 index 0000000..b959744 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +vars_plugins_enabled = host_group_vars,community.sops.sops diff --git a/playbooks/systems/k3s-bootstrap/playbook.yml b/playbooks/systems/k3s-bootstrap/playbook.yml new file mode 100644 index 0000000..1d692c3 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/playbook.yml @@ -0,0 +1,22 @@ +--- +- name: Cluster prep + hosts: k3s_cluster + remote_user: admin + gather_facts: true + become: true + roles: + - role: ./.ansible/roles/k3s-ansible/roles/prereq + +- name: Setup K3S server + hosts: server + remote_user: admin + become: true + roles: + - role: ./.ansible/roles/k3s-ansible/roles/k3s_server + +- name: Setup K3S agent + hosts: agent + remote_user: admin + become: true + roles: + - role: ./.ansible/roles/k3s-ansible/roles/k3s_agent diff --git a/playbooks/systems/k3s-bootstrap/tasks/netplan.yaml b/playbooks/systems/k3s-bootstrap/tasks/netplan.yaml new file mode 100644 index 0000000..8f3c41f --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/tasks/netplan.yaml @@ -0,0 +1,32 @@ +--- +- name: Netplan config + when: ansible_distribution == 'Ubuntu' + block: + - name: Update the netplan config + become: true + ansible.builtin.template: + src: templates/60-floating-ip.j2 + dest: /etc/netplan/60-floating-ip.yaml + owner: root + group: root + mode: '0600' + vars: + publicIP: 195.201.250.50 + + - name: Apply a new config + become: true + ansible.builtin.shell: netplan apply +- name: Ifcfg onfig + when: ansible_distribution == 'Rocky' + block: + - name: Update the ifcfg config + notify: Restart the network service + become: true + ansible.builtin.template: + src: templates/ifcfg-eth0:1.j2 + dest: /etc/sysconfig/network-scripts/ifcfg-eth0:1 + owner: root + group: root + mode: '0600' + vars: + publicIP: 195.201.249.91 diff --git a/playbooks/systems/k3s-bootstrap/tasks/packages.yaml b/playbooks/systems/k3s-bootstrap/tasks/packages.yaml new file mode 100644 index 0000000..7462263 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/tasks/packages.yaml @@ -0,0 +1,79 @@ +- name: Update ubuntu repos + when: ansible_distribution == 'Ubuntu' + become: true + ansible.builtin.apt: + update_cache: true + +- name: Update yum repos + when: ansible_distribution == 'Fedora' or ansible_distribution == 'Rocky' + become: true + ansible.builtin.yum: + update_cache: true + +# --------------------------------------------------------------------- +# Upgrade system if it's the first ansible run +# --------------------------------------------------------------------- +- name: Check that the ansible_initialized file exists + become: true + stat: + path: /etc/ansible_initialized + register: stat_result +- name: Upgrade system + when: not stat_result.stat.exists + block: + - name: Upgrade ubuntu repos + when: ansible_distribution == 'Ubuntu' + become: true + ansible.builtin.apt: + upgrade: full + + - name: Upgrade all packages + when: ansible_distribution == 'Fedora' or ansible_distribution == 'Rocky' + become: true + ansible.builtin.yum: + name: '*' + state: latest + + - name: Reboot to apply upgrades + when: not stat_result.stat.exists + become: true + ansible.builtin.reboot: + + - name: Create the ansible_initialized file + become: true + file: + path: /etc/ansible_initialized + state: touch + when: not stat_result.stat.exists + +- name: Install host-specific packages + when: packages is defined + become: true + ansible.builtin.package: + name: "{{ item.name }}" + state: "{{ item.state }}" + loop: "{{ packages }}" + +- name: Install common packages + become: true + ansible.builtin.package: + name: "{{ item.name }}" + state: "{{ item.state }}" + loop: "{{ common_packages }}" + +- name: Configure host-specific services + when: services is defined + become: true + ansible.builtin.systemd: + state: "{{ item.state }}" + name: "{{ item.name }}" + enabled: "{{ item.enabled }}" + loop: "{{ services }}" + +- name: Configure common services + become: true + ansible.builtin.systemd: + state: "{{ item.state }}" + name: "{{ item.name }}" + enabled: "{{ item.enabled }}" + loop: "{{ common_services }}" diff --git a/playbooks/systems/k3s-bootstrap/tasks/sysctl.yaml b/playbooks/systems/k3s-bootstrap/tasks/sysctl.yaml new file mode 100644 index 0000000..e6e6890 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/tasks/sysctl.yaml @@ -0,0 +1,20 @@ +- name: Install host-specific sysctl + when: sysctl is defined + become: true + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_set: "{{ item.set }}" + state: "{{ item.state }}" + reload: true + loop: "{{ sysctl }}" + +- name: Install common sysctl + become: true + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_set: "{{ item.set }}" + state: "{{ item.state }}" + reload: true + loop: "{{ common_sysctl }}" diff --git a/playbooks/systems/k3s-bootstrap/tasks/users.yaml b/playbooks/systems/k3s-bootstrap/tasks/users.yaml new file mode 100644 index 0000000..2995d9e --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/tasks/users.yaml @@ -0,0 +1,33 @@ +- name: Prepare global users + block: + - name: Ensure required groups exist + ansible.builtin.group: + name: "{{ item.name }}" + state: "{{ item.state }}" + loop: "{{ user_groups }}" + + - name: Allow passwordless sudo for certain groups + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: '^%wheel' + line: '%wheel ALL=(ALL) NOPASSWD: ALL' + validate: 'visudo -cf %s' + when: 'item.sudo' + loop: "{{ user_groups }}" + + - name: Create the users user + ansible.builtin.user: + name: "{{ item.name }}" + shell: /bin/bash + groups: "{% for grp in item.groups %}{{ grp }}{% if not loop.last %},{% endif %}{% endfor %}" + append: false + password: + loop: "{{ users }}" + + - name: Set authorized keys for user + ansible.posix.authorized_key: + user: "{{ item.0.name }}" + state: "{{ item.1.state }}" + key: "{{ item.1.key }}" + loop: "{{ users | subelements('ssh_keys') }}" diff --git a/playbooks/systems/k3s-bootstrap/tasks/volumes.yaml b/playbooks/systems/k3s-bootstrap/tasks/volumes.yaml new file mode 100644 index 0000000..033fb03 --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/tasks/volumes.yaml @@ -0,0 +1,33 @@ +# ----------------------------------- +# -- Configure volumes +# ----------------------------------- +- name: Configure volumes + when: volumes is defined + block: + - name: Ensure volumes exist + become: true + community.general.parted: + device: "{{ item.device }}" + unit: KiB + loop: "{{ volumes }}" + - name: Create a directory if it does not exist + become: true + ansible.builtin.file: + path: "{{ item.dir }}" + state: directory + mode: '0755' + loop: "{{ volumes }}" + - name: Create a ext4 filesystem + become: true + community.general.filesystem: + fstype: "{{ item.fstype }}" + dev: "{{ item.device }}" + loop: "{{ volumes }}" + - name: Remount volume to another location + become: true + ansible.posix.mount: + path: "{{ item.dir }}" + src: "{{ item.device }}" + state: mounted + fstype: "{{ item.fstype }}" + loop: "{{ volumes }}" diff --git a/playbooks/systems/k3s-bootstrap/templates/60-floating-ip.j2 b/playbooks/systems/k3s-bootstrap/templates/60-floating-ip.j2 new file mode 100644 index 0000000..47b61ef --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/templates/60-floating-ip.j2 @@ -0,0 +1,8 @@ +--- +network: + version: 2 + renderer: networkd + ethernets: + eth0: + addresses: + - {{ publicIP }}/32 diff --git a/playbooks/systems/k3s-bootstrap/templates/ifcfg-eth0:1.j2 b/playbooks/systems/k3s-bootstrap/templates/ifcfg-eth0:1.j2 new file mode 100644 index 0000000..6b385ff --- /dev/null +++ b/playbooks/systems/k3s-bootstrap/templates/ifcfg-eth0:1.j2 @@ -0,0 +1,7 @@ + BOOTPROTO=static + DEVICE=eth0:1 + IPADDR={{ publicIP }} + PREFIX=32 + TYPE=Ethernet + USERCTL=no + ONBOOT=yes diff --git a/playbooks/systems/system-bootstrap/.gitignore b/playbooks/systems/system-bootstrap/.gitignore new file mode 100644 index 0000000..655f652 --- /dev/null +++ b/playbooks/systems/system-bootstrap/.gitignore @@ -0,0 +1,5 @@ +# ---> Ansible +*.retry + +# ---> Python +venv diff --git a/playbooks/systems/system-bootstrap/LICENSE b/playbooks/systems/system-bootstrap/LICENSE new file mode 100644 index 0000000..d5075a3 --- /dev/null +++ b/playbooks/systems/system-bootstrap/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the +above copyright notice and the following two paragraphs appear in +all copies of this software. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN +IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/playbooks/systems/system-bootstrap/Makefile b/playbooks/systems/system-bootstrap/Makefile new file mode 100644 index 0000000..c553754 --- /dev/null +++ b/playbooks/systems/system-bootstrap/Makefile @@ -0,0 +1,23 @@ +check: venv-install-deps venv-install-reqs venv-check + +run: venv venv-install-deps venv-install-reqs venv-run + +venv: + python3 -m venv venv + +venv-install-deps: + source venv/bin/activate &&\ + python3 -m pip install hcloud ansible + +venv-install-reqs: + source venv/bin/activate &&\ + python3 -m ansible galaxy install -r ./requirements.yml + +venv-check: + source venv/bin/activate &&\ + python3 -m ansible playbook playbook.yml --check -i ./inventory.yaml + +venv-run: + source venv/bin/activate &&\ + python3 -m ansible playbook playbook.yml -i /tmp/outputs/inventory.yaml + diff --git a/playbooks/systems/system-bootstrap/README.md b/playbooks/systems/system-bootstrap/README.md new file mode 100644 index 0000000..1db5409 --- /dev/null +++ b/playbooks/systems/system-bootstrap/README.md @@ -0,0 +1,22 @@ +# systems-ansible-playbooks + +Manage servers with ansible + +## K3s + +I'm not configuring k3s with ansble, it's configured with following flags + +curl -sfL https://get.k3s.io | sh -s - server \ + --write-kubeconfig-mode=644 \ + --node-name="$(hostname -f)" \ + --kubelet-arg "allowed-unsafe-sysctls=net.ipv4.ip_forward" \ + --tls-san="$(hostname -I | awk '{print $1}')" \ + --disable-cloud-controller \ + --disable-helm-controller \ + --disable metrics-server \ + --disable local-storage \ + --disable traefik \ + --cluster-cidr="10.244.0.0/16" \ + --flannel-backend none \ + --disable coredns \ + --cluster-init \ diff --git a/playbooks/systems/system-bootstrap/ansible.cfg b/playbooks/systems/system-bootstrap/ansible.cfg new file mode 100644 index 0000000..b959744 --- /dev/null +++ b/playbooks/systems/system-bootstrap/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +vars_plugins_enabled = host_group_vars,community.sops.sops diff --git a/playbooks/systems/system-bootstrap/playbook.yml b/playbooks/systems/system-bootstrap/playbook.yml new file mode 100644 index 0000000..a6950f3 --- /dev/null +++ b/playbooks/systems/system-bootstrap/playbook.yml @@ -0,0 +1,25 @@ +--- +- name: Prepare users + hosts: servers + remote_user: root + vars_files: + - ../../../group_vars/users.yaml + tags: + - system + tasks: + - name: Prepare Users + ansible.builtin.import_tasks: ./tasks/users.yaml + +- name: Prepare systems + hosts: servers + remote_user: admin + vars_files: + - ../../../group_vars/packages.yaml + - ../../../group_vars/sysctl.yaml + tags: + - system + tasks: + - name: Install packages + ansible.builtin.import_tasks: ./tasks/packages.yaml + - name: Setup sysctl + ansible.builtin.import_tasks: ./tasks/sysctl.yaml diff --git a/playbooks/systems/system-bootstrap/tasks/netplan.yaml b/playbooks/systems/system-bootstrap/tasks/netplan.yaml new file mode 100644 index 0000000..8f3c41f --- /dev/null +++ b/playbooks/systems/system-bootstrap/tasks/netplan.yaml @@ -0,0 +1,32 @@ +--- +- name: Netplan config + when: ansible_distribution == 'Ubuntu' + block: + - name: Update the netplan config + become: true + ansible.builtin.template: + src: templates/60-floating-ip.j2 + dest: /etc/netplan/60-floating-ip.yaml + owner: root + group: root + mode: '0600' + vars: + publicIP: 195.201.250.50 + + - name: Apply a new config + become: true + ansible.builtin.shell: netplan apply +- name: Ifcfg onfig + when: ansible_distribution == 'Rocky' + block: + - name: Update the ifcfg config + notify: Restart the network service + become: true + ansible.builtin.template: + src: templates/ifcfg-eth0:1.j2 + dest: /etc/sysconfig/network-scripts/ifcfg-eth0:1 + owner: root + group: root + mode: '0600' + vars: + publicIP: 195.201.249.91 diff --git a/playbooks/systems/system-bootstrap/tasks/packages.yaml b/playbooks/systems/system-bootstrap/tasks/packages.yaml new file mode 100644 index 0000000..44bce8b --- /dev/null +++ b/playbooks/systems/system-bootstrap/tasks/packages.yaml @@ -0,0 +1,95 @@ +- name: Update ubuntu repos + when: ansible_distribution == 'Ubuntu' + become: true + ansible.builtin.apt: + update_cache: true + +- name: Add Kubernetes repo + when: ansible_distribution == 'Fedora' or ansible_distribution == 'Rocky' + become: true + template: + src: kubernetes.repo.j2 + dest: /etc/yum.repos.d/kubernetes.repo + force: true + +- name: Add Docker repo + when: ansible_distribution == 'Fedora' or ansible_distribution == 'Rocky' + become: true + template: + src: docker.repo.j2 + dest: /etc/yum.repos.d/docker.repo + force: true + +- name: Update yum repos + when: ansible_distribution == 'Fedora' or ansible_distribution == 'Rocky' + become: true + ansible.builtin.yum: + update_cache: true +# --------------------------------------------------------------------- +# Upgrade system if it's the first ansible run +# --------------------------------------------------------------------- +- name: Check that the ansible_initialized file exists + become: true + stat: + path: /etc/ansible_initialized + register: stat_result + +- name: Upgrade system + when: not stat_result.stat.exists + block: + - name: Upgrade ubuntu repos + when: ansible_distribution == 'Ubuntu' + become: true + ansible.builtin.apt: + upgrade: full + + - name: Upgrade all packages + when: ansible_distribution == 'Fedora' or ansible_distribution == 'Rocky' + become: true + ansible.builtin.yum: + name: '*' + state: latest + + - name: Reboot to apply upgrades + when: not stat_result.stat.exists + become: true + ansible.builtin.reboot: + + - name: Create the ansible_initialized file + become: true + file: + path: /etc/ansible_initialized + state: touch + when: not stat_result.stat.exists + +- name: Install host-specific packages + when: packages is defined + become: true + ansible.builtin.package: + name: "{{ item.name }}" + state: "{{ item.state }}" + loop: "{{ packages }}" + +- name: Install common packages + become: true + ansible.builtin.package: + name: "{{ item.name }}" + state: "{{ item.state }}" + loop: "{{ common_packages }}" + +- name: Configure host-specific services + when: services is defined + become: true + ansible.builtin.systemd: + state: "{{ item.state }}" + name: "{{ item.name }}" + enabled: "{{ item.enabled }}" + loop: "{{ services }}" + +- name: Configure common services + become: true + ansible.builtin.systemd: + state: "{{ item.state }}" + name: "{{ item.name }}" + enabled: "{{ item.enabled }}" + loop: "{{ common_services }}" diff --git a/playbooks/systems/system-bootstrap/tasks/sysctl.yaml b/playbooks/systems/system-bootstrap/tasks/sysctl.yaml new file mode 100644 index 0000000..e6e6890 --- /dev/null +++ b/playbooks/systems/system-bootstrap/tasks/sysctl.yaml @@ -0,0 +1,20 @@ +- name: Install host-specific sysctl + when: sysctl is defined + become: true + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_set: "{{ item.set }}" + state: "{{ item.state }}" + reload: true + loop: "{{ sysctl }}" + +- name: Install common sysctl + become: true + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_set: "{{ item.set }}" + state: "{{ item.state }}" + reload: true + loop: "{{ common_sysctl }}" diff --git a/playbooks/systems/system-bootstrap/tasks/users.yaml b/playbooks/systems/system-bootstrap/tasks/users.yaml new file mode 100644 index 0000000..2995d9e --- /dev/null +++ b/playbooks/systems/system-bootstrap/tasks/users.yaml @@ -0,0 +1,33 @@ +- name: Prepare global users + block: + - name: Ensure required groups exist + ansible.builtin.group: + name: "{{ item.name }}" + state: "{{ item.state }}" + loop: "{{ user_groups }}" + + - name: Allow passwordless sudo for certain groups + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: '^%wheel' + line: '%wheel ALL=(ALL) NOPASSWD: ALL' + validate: 'visudo -cf %s' + when: 'item.sudo' + loop: "{{ user_groups }}" + + - name: Create the users user + ansible.builtin.user: + name: "{{ item.name }}" + shell: /bin/bash + groups: "{% for grp in item.groups %}{{ grp }}{% if not loop.last %},{% endif %}{% endfor %}" + append: false + password: + loop: "{{ users }}" + + - name: Set authorized keys for user + ansible.posix.authorized_key: + user: "{{ item.0.name }}" + state: "{{ item.1.state }}" + key: "{{ item.1.key }}" + loop: "{{ users | subelements('ssh_keys') }}" diff --git a/playbooks/systems/system-bootstrap/tasks/volumes.yaml b/playbooks/systems/system-bootstrap/tasks/volumes.yaml new file mode 100644 index 0000000..033fb03 --- /dev/null +++ b/playbooks/systems/system-bootstrap/tasks/volumes.yaml @@ -0,0 +1,33 @@ +# ----------------------------------- +# -- Configure volumes +# ----------------------------------- +- name: Configure volumes + when: volumes is defined + block: + - name: Ensure volumes exist + become: true + community.general.parted: + device: "{{ item.device }}" + unit: KiB + loop: "{{ volumes }}" + - name: Create a directory if it does not exist + become: true + ansible.builtin.file: + path: "{{ item.dir }}" + state: directory + mode: '0755' + loop: "{{ volumes }}" + - name: Create a ext4 filesystem + become: true + community.general.filesystem: + fstype: "{{ item.fstype }}" + dev: "{{ item.device }}" + loop: "{{ volumes }}" + - name: Remount volume to another location + become: true + ansible.posix.mount: + path: "{{ item.dir }}" + src: "{{ item.device }}" + state: mounted + fstype: "{{ item.fstype }}" + loop: "{{ volumes }}" diff --git a/playbooks/systems/system-bootstrap/templates/60-floating-ip.j2 b/playbooks/systems/system-bootstrap/templates/60-floating-ip.j2 new file mode 100644 index 0000000..47b61ef --- /dev/null +++ b/playbooks/systems/system-bootstrap/templates/60-floating-ip.j2 @@ -0,0 +1,8 @@ +--- +network: + version: 2 + renderer: networkd + ethernets: + eth0: + addresses: + - {{ publicIP }}/32 diff --git a/playbooks/systems/system-bootstrap/templates/docker.repo.j2 b/playbooks/systems/system-bootstrap/templates/docker.repo.j2 new file mode 100644 index 0000000..2605574 --- /dev/null +++ b/playbooks/systems/system-bootstrap/templates/docker.repo.j2 @@ -0,0 +1,6 @@ +[docker-ce-stable] +name=Docker CE Stable - $basearch +baseurl=https://download.docker.com/linux/centos/$releasever/$basearch/stable +enabled=1 +gpgcheck=1 +gpgkey=https://download.docker.com/linux/centos/gpg diff --git a/playbooks/systems/system-bootstrap/templates/ifcfg-eth0:1.j2 b/playbooks/systems/system-bootstrap/templates/ifcfg-eth0:1.j2 new file mode 100644 index 0000000..6b385ff --- /dev/null +++ b/playbooks/systems/system-bootstrap/templates/ifcfg-eth0:1.j2 @@ -0,0 +1,7 @@ + BOOTPROTO=static + DEVICE=eth0:1 + IPADDR={{ publicIP }} + PREFIX=32 + TYPE=Ethernet + USERCTL=no + ONBOOT=yes diff --git a/playbooks/systems/system-bootstrap/templates/kubernetes.repo.j2 b/playbooks/systems/system-bootstrap/templates/kubernetes.repo.j2 new file mode 100644 index 0000000..fb52c55 --- /dev/null +++ b/playbooks/systems/system-bootstrap/templates/kubernetes.repo.j2 @@ -0,0 +1,6 @@ +[kubernetes] +name=Kubernetes +baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/ +enabled=1 +gpgcheck=1 +gpgkey=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/repodata/repomd.xml.key diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..161f116 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +hcloud +ansible diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..8e4d0aa --- /dev/null +++ b/requirements.yml @@ -0,0 +1,15 @@ +--- +collections: + - hetzner.hcloud + - community.sops + - name: ansible.utils + - name: community.general + version: 8.5.0 + - name: ansible.posix + - name: kubernetes.core +roles: + - src: git+https://git.badhouseplants.net/allanger/k3s-ansible.git + version: master + - src: git+https://git.badhouseplants.net/allanger/ansible-create-k8s-user.git + version: download-config + - xanmanning.k3s diff --git a/resources/known_hosts b/resources/known_hosts new file mode 100644 index 0000000..5f43748 --- /dev/null +++ b/resources/known_hosts @@ -0,0 +1,2 @@ +git.badhouseplants.net ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDTsGvo2KESuvWLf4QEwg+1DdetksEgowGAW04OezLhpA6bqT/6WIx0O6NaQCSN+ZxBRV2a/+ICSGFtU+RFTtbcu30Lcsl/yD9qwbzBqNTUweaWPNze5LrHbIV0/J8nTSt4vzCw3eF0wdvnIQWLhdjQ4qjtV1OWlYJDwo1rHPDJ5JAuYKeLz50iEhs7R6oj2ccws2olGUssG4voi/EHWVxQa3khmSNWdHTNR6H6sdkehKWflGokS5oRf5dIUFj+BbFbq6JxJQXzoiVzOoLdfGz1S6m5i1O0UbDgnoL/my+0ZiswbIa5ApbP+oOjvW5NWF1lzehowNj7Kd1Ow9+FN+2eHSPN+g5rXapBNclQ5/JT5pcSP3vjVuvNoUE/GHhLLFLG3nsPd0GuSB9PDWqxBdnAybOjjldL2cTdDajcdkYbyUGT5HCvdjgpCeJe22JQElvTgKWnICDsnCaIXPwrp1ek5soAjY6ylsOv2KYUnoo+gy7P0+Bzf9JzngfgcN/4x5s8keRQ6YNC9I3NiN1XRdJA52/RGMQfuWjqcqG04yk6AHZZLD3OdBJRjLhXSRn4/DixQ6BxT25mZfZVuyS9pOuDXaI9ZmYtX+xSXAp1XTfIH6t/ud1A+jTKbrCeV5AS6CmlGVfge+9ewDw7RRGd3MW5i5N3N7RwJVMfpcTXVPY2sQ== +