Home K8S setup

This commit is contained in:
2021-03-13 00:23:06 +00:00
commit 8a5296a104
47 changed files with 3098 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
step1/static/alpine-aarch64.tar.gz
step2/static/token_file
step2/static/kubectl.conf
#
*/traefik-static-config.yaml
*terraform.tfstate*
*.log
*/.terraform/
+71
View File
@@ -0,0 +1,71 @@
# Raspberry Pi kubernetes cluster on Alpine linux setup
This repository is supposed to help if I'd EVER AGAIN destroy the whole cluster by accident.
Code is a result of 6 hours coding marathon, but will be improved and patched in future.
## Usage
### Memory card preparations [step1]
To prepare memory cards check the **step1** directory.
If your Pi memory card is present under /dev/disk5 - you don't need to change anything, otherwise change it to the device of your choice.
```bash
PI_CARD="/dev/disk5"
```
Run the following script which will:
* Format memory card
* Split it into two partitions (1G + remaining)
* Copy basic Alpine system onto 1G partition
* Add an overlay allowing ethernet interface get up and SSH root access without the password
```bash
001-prepare-card.sh
```
### Cluster preparations [step2]
#### Before
* Modify pi-hosts.txt file and adjust it to your setup.
* Modify address class in step2/static/k8s-metallb-dashboard-config.yaml to suit your network
Add following to your ~/.ssh/config file
```bash
Host pi?
User root
Hostname %h.local
```
#### Prepare your nodes for Ansible
```bash
001-prepare-ansible.sh
```
#### Run the playbook
```bash
ansible-playbook rpi.yaml -f 10
```
### K8S definitions [step3]
Use makefile from step3 to apply / destroy resources
#### Required environment variables:
```bash
TRAEFIK_API_KEY // Traefik API key
GH_USER // GitHub user
GH_PAT // GitHub Personal Access Token
```
## Outcome
* Raspberry Pi cluster running Alpine linux as a base
* MetalLB for load balancing in your LAN
* Kubernetes dashboard installed and exposed via MetalLB
* Traefik managing the.. traffic
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# This script
# - prepares raspberry pi memory card ( /dev/disk5 )
# - partitions the memory card
# - unpacks RPI rpi base system
# - applies headless boot configuration with network interface and ssh agent accepting connections without password
PI_CARD="/dev/disk5"
ALPINE_BASE_IMAGE="https://dl-cdn.alpinelinux.org/alpine/v3.13/releases/aarch64/alpine-rpi-3.13.2-aarch64.tar.gz"
# [[ -f /Volumes/RPI0 ]] && echo "Deleting leftover directory"; rm -fr /Volumes/RPI0 || echo "Clean and dandy on the Volumes front"
[[ ! -f static/alpine-aarch64.tar.gz ]] && wget $ALPINE_BASE_IMAGE -O static/alpine-aarch64.tar.gz || echo "Alpine baseimg already exists"
sudo diskutil eraseDisk FAT32 RPI MBRFormat $PI_CARD
sudo diskutil partitionDisk /dev/$PI_CARD MBRFormat FAT32 RPI0 1g FREE RPISYS 0b
sudo tar xf static/alpine-aarch64.tar.gz -C /Volumes/RPI0
sudo cp static/headless.tar.gz /Volumes/RPI0/headless.apkovl.tar.gz
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
KEYMAPOPTS="us us-intl"
HOSTNAMEOPTS="-n alpine-test"
INTERFACESOPTS="auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
hostname alpine-test
"
DNSOPTS="-d local"
TIMEZONEOPTS="-z UTC"
APKREPOSOPTS="-f"
SSHDOPTS="-c openssh"
NTPOPTS="-c openntpd"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
AVAILABLE_HOSTS=$(grep "\." pi-hosts.txt | awk '{print $2}' | cut -d "=" -f2)
for HOST in $AVAILABLE_HOSTS; do
echo "Working on host $HOST"
ssh -t root@$HOST 'setup-ntp -c chrony; setup-apkrepos -f; apk update; apk add python3'
done
+4
View File
@@ -0,0 +1,4 @@
all:
./001-prepare-ansible.sh
ansible-playbook rpi.yaml -f 10
+8
View File
@@ -0,0 +1,8 @@
[defaults]
host_key_checking = False
remote_user = root
inventory = ./pi-hosts.txt
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
+9
View File
@@ -0,0 +1,9 @@
[masters]
pi0 ansible_host=192.168.50.132 # Pi0
[workers]
pi1 ansible_host=192.168.50.135 # Pi1
pi3 ansible_host=192.168.50.60 # Pi3
pi4 ansible_host=192.168.50.36 # Pi4
pi2 ansible_host=192.168.50.85 # Pi2
pi5 ansible_host=192.168.50.230 # Pi5
@@ -0,0 +1,16 @@
---
- name: Taint nodes
ansible.builtin.shell:
cmd: |
kubectl taint nodes --all node-role.kubernetes.io/master:NoSchedule- || true
- name: Install py3-pip
ansible.builtin.package:
name:
- py3-pip
- helm
state: present
- name: Install yaml package
pip:
name: PyYAML
+5
View File
@@ -0,0 +1,5 @@
---
# - name: Github authentication
# ansible.builtin.shell:
# cmd: |
# kubectl create secret docker-registry docker-ghcr --docker-server=https://ghcr.io --docker-username=lukaszraczylo --docker-password=$GITHUB_TOKEN [email protected]
+3
View File
@@ -0,0 +1,3 @@
---
# - include: k8s-general-setup.yml
- include: k8s-github.yml
+94
View File
@@ -0,0 +1,94 @@
---
- name: Install packages - filesystem
ansible.builtin.package:
name:
- e2fsprogs
- parted
state: present
- name: Create partition for perm data
community.general.parted:
device: /dev/mmcblk0
number: 2
fs_type: ext4
part_start: 1GiB
part_end: 100%
flags: [ boot ]
state: present
- name: Fomat perm data partition
community.general.filesystem:
dev: /dev/mmcblk0p2
fstype: ext4
ignore_errors: yes
- name: Re-mount old data partition
ansible.posix.mount:
path: /media/mmcblk0p1
src: /dev/mmcblk0p1
opts: rw
state: remounted
- name: Mount perm data partition
ansible.posix.mount:
path: /mnt
src: /dev/mmcblk0p2
opts: rw
fstype: ext4
state: mounted
- name: Overwrite setup-disk
ansible.builtin.copy:
src: static/setup-disk
dest: /sbin/setup-disk
owner: root
group: root
mode: '0744'
backup: yes
- name: Setup new disk as system
ansible.builtin.command:
cmd: setup-disk -m sys /mnt
creates: /mnt/bin/date
- name: Housekeeping
ansible.builtin.shell:
cmd: |
rm -f /media/mmcblk0p1/boot/*
cd /mnt
rm boot/boot
mv boot/* /media/mmcblk0p1/boot/
rm -Rf boot
mkdir media/mmcblk0p1
ln -s media/mmcblk0p1/boot boot
removes: /mnt/boot/boot
- name: Add mountpoints to fstab
ansible.builtin.shell:
cmd: |
echo "/dev/mmcblk0p1 /media/mmcblk0p1 vfat defaults 0 0" >> etc/fstab
echo "/dev/mmcblk0p1 /media/mmcblk0p1 vfat defaults 0 0" >> /etc/fstab
sed -i '/cdrom/d' etc/fstab
sed -i '/floppy/d' etc/fstab
sed -i '/usbdisk/d' etc/fstab
touch /mnt/.nvm_mountpoints
chdir: /mnt
creates: /mnt/.nvm_mountpoints
- name: Enable repositories and cgroups
ansible.builtin.shell:
cmd: |
sed -i '/edge/s/^#//' /mnt/etc/apk/repositories
sed -i 's/^/root=\/dev\/mmcblk0p2 /' /media/mmcblk0p1/cmdline.txt
echo "cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1" >> /media/mmcblk0p1/cmdline.txt
sed -i ':a;N;$!ba;s/\n/ /g' /media/mmcblk0p1/cmdline.txt
touch /mnt/.nvm_cgroups
creates: /mnt/.nvm_cgroups
- name: Commit changes
ansible.builtin.shell:
chdir: /media/mmcblk0p1
cmd: lbu_commit -d || true
- name: Reboot
reboot:
+11
View File
@@ -0,0 +1,11 @@
---
- name: Pull basic images
ansible.builtin.shell:
cmd: kubeadm config images pull
- name: Install helm
ansible.builtin.shell:
cmd: |
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
creates: /etc/kubernetes/kubelet.conf
register: k8sinit_output
+9
View File
@@ -0,0 +1,9 @@
---
- name: Check for base system config lockfile
stat: path="/mnt/.nvm_mountpoints"
register: basic_config_lock
- include: filesystem.yml
when: basic_config_lock.stat.exists == False
- include: networking.yml
- include: kubernetes.yml
- include: nfs.yml
+68
View File
@@ -0,0 +1,68 @@
---
- name: Set hostname
ansible.builtin.hostname:
name: '{{ inventory_hostname }}'
- name: "Build hosts file"
lineinfile:
dest: /etc/hosts
regexp: '.*{{ item }}$'
line: "{{hostvars[item]['ansible_default_ipv4']['address']}} {{item}} {{item}}.local"
state: present
register: etchostsupdate
when: hostvars[item]['ansible_facts']['default_ipv4'] is defined
with_items:
- "{{ groups['all'] }}"
- name: Install packages for networking
ansible.builtin.package:
name:
- dbus
- avahi
- kubernetes
- kubectl
- docker
- cni-plugins
- kubelet
- kubeadm
- sudo
- curl
state: present
- name: Enable service dbus
ansible.builtin.service:
name: dbus
state: started
enabled: yes
- name: Overwrite avahi config
ansible.builtin.copy:
src: static/avahi-config
dest: /etc/avahi/avahi-daemon.conf
owner: root
group: root
mode: '0644'
backup: yes
- name: Enable service avahi
ansible.builtin.service:
name: avahi-daemon
state: restarted
enabled: yes
- name: Enable service docker
ansible.builtin.service:
name: docker
state: started
enabled: yes
- name: Enable service kubelet
ansible.builtin.service:
name: kubelet
state: started
enabled: yes
- name: Enable promisc mode for ARP requests
ansible.builtin.shell:
cmd: |
ip link set eth0 promisc on
+34
View File
@@ -0,0 +1,34 @@
---
- name: Install nfsprogs
ansible.builtin.package:
name:
- nfs-utils
- name: Prepare nfs mountpoint
ansible.builtin.shell:
cmd: |
mkdir -p /media/nfs
- name: Mount NFS resources
ansible.posix.mount:
src: "{{settings.nfs_host}}:{{settings.nfs_multi_share_path}}"
path: /media/nfs
fstype: nfs
opts: nofail,noatime,nolock,tcp,actimeo=1800
state: mounted
- name: Mount NFS torrent
ansible.posix.mount:
src: "{{settings.nfs_host}}:{{settings.nfs_torrent_path}}"
path: /mnt/drobo-storage/docker-volumes/torrent
fstype: nfs
opts: nofail,noatime,nolock,tcp,actimeo=1800
state: mounted
- name: Mount NFS adguard
ansible.posix.mount:
src: "{{settings.nfs_host}}:{{settings.nfs_adguard_path}}"
path: /mnt/drobo-storage/docker-volumes/adguard
fstype: nfs
opts: nofail,noatime,nolock,tcp,actimeo=1800
state: mounted
+2
View File
@@ -0,0 +1,2 @@
---
- include: master-setup.yml
@@ -0,0 +1,35 @@
---
- name: Initialize cluster
ansible.builtin.shell:
cmd: |
kubeadm reset -f
kubeadm init --pod-network-cidr=10.244.0.0/16
creates: /etc/kubernetes/kubelet.conf
register: k8sinit_output
- name: Storing Logs and Generated token for nodes config
ansible.builtin.copy:
content: '{{ k8sinit_output.stdout }}'
dest: cluster_join_file
when: '"token" in k8sinit_output.stdout'
- name: Pulling the token file for setup purposes
ansible.builtin.fetch:
flat: yes
src: cluster_join_file
dest: static/token_file
when: '"token" in k8sinit_output.stdout'
- name: Copying kubeconfig file
ansible.builtin.shell:
cmd: |
mkdir -p $HOME/.kube
cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
- name: Save kubeconfig locally
ansible.builtin.fetch:
flat: yes
src: /etc/kubernetes/admin.conf
dest: static/kubectl.conf
fail_on_missing: yes
+2
View File
@@ -0,0 +1,2 @@
---
- include: worker-setup.yml
@@ -0,0 +1,31 @@
---
- name: Copy token file to workers
ansible.builtin.copy:
src: static/token_file
dest: /root/cluster_token_file
owner: root
group: root
mode: '0644'
- name: Joining worker nodes with kubernetes master
ansible.builtin.shell:
cmd: |
kubeadm reset -f
cat /root/cluster_token_file | tail -2 > out.sh
sh out.sh
creates: out.sh
- name: Copy kubectl config to workers
ansible.builtin.copy:
src: static/kubectl.conf
dest: /root/kubectl-config
owner: root
group: root
mode: '0644'
- name: Copying kubeconfig file
ansible.builtin.shell:
cmd: |
mkdir -p $HOME/.kube
cp -f /root/kubectl-config $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
+22
View File
@@ -0,0 +1,22 @@
---
- hosts: all
vars:
settings:
nfs_host: 192.168.50.5
nfs_torrent_path: "/mnt/drobo-storage/docker-volumes/torrent"
nfs_adguard_path: "/mnt/drobo-storage/docker-volumes/adguard"
nfs_multi_share_path: "/mnt/drobo-storage/docker-volumes/nfs-share"
roles:
- common
- hosts: masters
roles:
- kubemaster
- hosts: workers
roles:
- kubeworkers
- hosts: masters
roles:
- basics
+49
View File
@@ -0,0 +1,49 @@
[server]
#host-name=foo
domain-name=local
#browse-domains=0pointer.de, zeroconf.org
use-ipv4=yes
use-ipv6=yes
#allow-interfaces=eth0
#deny-interfaces=eth1
#check-response-ttl=no
#use-iff-running=no
#enable-dbus=yes
#disallow-other-stacks=no
#allow-point-to-point=no
#cache-entries-max=4096
#clients-max=4096
#objects-per-client-max=1024
#entries-per-entry-group-max=32
ratelimit-interval-usec=1000000
ratelimit-burst=1000
[wide-area]
enable-wide-area=yes
[publish]
#disable-publishing=no
#disable-user-service-publishing=no
#add-service-cookie=no
#publish-addresses=yes
publish-hinfo=no
publish-workstation=no
#publish-domain=yes
#publish-dns-servers=192.168.50.1, 192.168.50.2
#publish-resolv-conf-dns-servers=yes
#publish-aaaa-on-ipv4=yes
#publish-a-on-ipv6=no
[reflector]
#enable-reflector=no
#reflect-ipv=no
#reflect-filters=_airplay._tcp.local,_raop._tcp.local
[rlimits]
#rlimit-as=
#rlimit-core=0
#rlimit-data=8388608
#rlimit-fsize=0
#rlimit-nofile=768
#rlimit-stack=8388608
#rlimit-nproc=3
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
**.terraform
+22
View File
@@ -0,0 +1,22 @@
ADDITIONAL_ARGS=-var 'traefik_api_key=$(TRAEFIK_API_KEY)' -var "github_user=$(GH_USER)" -var "github_pat=$(GH_TOKEN)"
apply:
cd infrastructure; terraform apply $(ADDITIONAL_ARGS) -auto-approve -var-file ../variables.tfvars
plan:
cd infrastructure; terraform plan $(ADDITIONAL_ARGS) -var-file ../variables.tfvars
destroy:
cd infrastructure; terraform destroy $(ADDITIONAL_ARGS) -var-file ../variables.tfvars
refresh:
cd infrastructure; terraform refresh $(ADDITIONAL_ARGS) -var-file ../variables.tfvars
init:
cd infrastructure; rm -fr .terraform; terraform init
import:
cd infrastructure; terraform import $(ADDITIONAL_ARGS) -var-file ../variables.tfvars $(ARGS)
lint:
terraform fmt -recursive infrastructure/
+231
View File
@@ -0,0 +1,231 @@
resource "kubernetes_storage_class" "adguard_nfs" {
metadata {
name = "nfs-adguard"
}
storage_provisioner = "local"
reclaim_policy = "Retain"
allow_volume_expansion = true
volume_binding_mode = "Immediate"
}
resource "kubernetes_namespace" "adguard_ns" {
metadata {
name = "adguard"
labels = {
app = "adguard"
}
}
}
resource "kubernetes_ingress" "adguard_ingress" {
metadata {
name = "traefik-adguard"
annotations = {
"kubernetes.io/ingress.class" : "traefik"
}
namespace = kubernetes_namespace.adguard_ns.metadata.0.name
}
spec {
rule {
host = "adguard.local"
http {
path {
path = "/"
backend {
service_name = kubernetes_service.adguard_svc.metadata.0.name
service_port = "adguard-http"
}
}
}
}
}
}
resource "kubernetes_service" "adguard_svc" {
depends_on = [kubernetes_config_map.metallb_cfg_map]
metadata {
namespace = kubernetes_namespace.adguard_ns.metadata.0.name
name = "adguard-fwd"
annotations = {
"metallb.universe.tf/address-pool" : "default"
}
}
spec {
port {
name = "adguard-dns-tcp"
port = 53
target_port = 53
}
port {
name = "adguard-dns-tls"
port = 853
target_port = 853
}
port {
name = "adguard-http"
port = 8080
target_port = 8080
}
port {
name = "adguard-init"
port = 3000
target_port = 3000
}
load_balancer_ip = "${var.network_subnet}.${var.adguard_catchall_host}"
selector = {
"app" : "adguard"
}
session_affinity = "ClientIP"
type = "LoadBalancer"
}
}
resource "kubernetes_service" "adguard_udp_svc" {
depends_on = [kubernetes_config_map.metallb_cfg_map]
metadata {
namespace = kubernetes_namespace.adguard_ns.metadata.0.name
name = "adguard-fwd-udp"
annotations = {
"metallb.universe.tf/address-pool" : "default"
}
}
spec {
port {
name = "adguard-dns-udp"
port = 53
target_port = 53
protocol = "UDP"
}
load_balancer_ip = "${var.network_subnet}.${var.adguard_host}"
selector = {
"app" : "adguard"
}
type = "LoadBalancer"
session_affinity = "ClientIP"
}
}
resource "kubernetes_persistent_volume" "adguard_shared_nfs" {
metadata {
name = "adguard-pv"
labels = {
name = "type"
value = "local"
}
}
spec {
capacity = {
storage = "2Gi"
}
storage_class_name = kubernetes_storage_class.adguard_nfs.metadata.0.name
access_modes = ["ReadWriteMany"]
persistent_volume_source {
host_path {
path = var.nfs_adguard_path
}
}
}
}
resource "kubernetes_persistent_volume_claim" "adguard_shared_nfs_claim" {
metadata {
name = "adguard-pvc"
namespace = kubernetes_namespace.adguard_ns.metadata.0.name
}
spec {
storage_class_name = kubernetes_storage_class.adguard_nfs.metadata.0.name
access_modes = ["ReadWriteMany"]
resources {
requests = {
storage = "2Gi"
}
}
volume_name = kubernetes_persistent_volume.adguard_shared_nfs.metadata.0.name
}
}
## adguard deployment
resource "kubernetes_deployment" "adguard" {
depends_on = [kubernetes_persistent_volume_claim.adguard_shared_nfs_claim]
metadata {
name = "adguard"
labels = {
app = "adguard"
}
namespace = kubernetes_namespace.adguard_ns.metadata.0.name
}
spec {
replicas = 1
selector {
match_labels = {
app = "adguard"
}
}
template {
metadata {
labels = {
app = "adguard"
}
}
spec {
volume {
name = "adguard-v-nfs"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.adguard_shared_nfs_claim.metadata.0.name
}
}
container {
name = "adguard"
image = "adguard/adguardhome:arm64-edge"
port {
container_port = 53
protocol = "TCP"
}
port {
container_port = 53
protocol = "UDP"
}
port {
container_port = 853
protocol = "TCP"
}
port {
container_port = 3000
protocol = "TCP"
}
port {
container_port = 8080
protocol = "TCP"
}
volume_mount {
mount_path = "/opt/adguardhome/work"
sub_path = "work-k8s"
name = "adguard-v-nfs"
}
volume_mount {
mount_path = "/opt/adguardhome/conf"
sub_path = "conf-k8s"
name = "adguard-v-nfs"
}
}
}
}
}
}
resource "null_resource" "adguard_crd" {
provisioner "local-exec" {
when = create
command = "kubectl apply -f ./static/crd-adguard-traefik-udp-53.yaml"
}
provisioner "local-exec" {
when = destroy
command = "kubectl delete -f ./static/crd-adguard-traefik-udp-53.yaml"
}
}
+123
View File
@@ -0,0 +1,123 @@
## Install metrics server
module "dashboard_metrics" {
depends_on = [kubernetes_service.traefik_svc]
source = "./modules/dashboard-metrics"
}
## Install dashboard
resource "helm_release" "k8s_dashboard" {
name = "k8s-dashboard"
namespace = "default"
repository = "https://kubernetes.github.io/dashboard/"
chart = "kubernetes-dashboard"
set {
name = "protocolHttp"
value = "true"
}
set {
name = "ingress.enabled"
value = "false"
}
set {
name = "rbac.create"
value = "true"
}
set {
name = "serviceAccount.create"
value = "true"
}
set {
name = "service.externalPort"
value = "1337"
}
set {
name = "networkPolicy.enabled"
value = "true"
}
set {
name = "podLabels.app"
value = "k8s-dashboard"
}
set {
name = "metrics-server.enabled"
value = "false"
}
set {
name = "metricsScraper.enabled"
value = "true"
}
set {
name = "metricsScraper.image.repository"
value = "kubernetesui/metrics-scraper-arm"
}
set {
name = "metricsScraper.image.tag"
value = "latest"
}
}
resource "kubernetes_cluster_role_binding" "k8s_dashboard_role" {
metadata {
name = "kubernetes-dashboard"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "cluster-admin"
}
subject {
kind = "ServiceAccount"
name = "k8s-dashboard-kubernetes-dashboard"
namespace = "default"
}
}
# Commented out as traffic runs via traefik anyway.
# resource "kubernetes_service" "k8s_dashboard_svc" {
# depends_on = [ kubernetes_config_map.metallb_cfg_map ]
# metadata {
# namespace = "default"
# name = "dashboard"
# annotations = {
# "metallb.universe.tf/address-pool": "default"
# }
# }
# spec {
# port {
# port = 80
# target_port = 9090
# }
# load_balancer_ip = "192.168.50.200"
# selector = {
# "app.kubernetes.io/instance": "k8s-dashboard"
# }
# type = "LoadBalancer"
# }
# }
resource "kubernetes_ingress" "traefik_dashboard_routing" {
metadata {
name = "traefik-k8s-dashboard"
annotations = {
"kubernetes.io/ingress.class" : "traefik"
}
namespace = "default"
}
spec {
rule {
host = "k8s.local"
http {
path {
path = "/"
backend {
service_name = "k8s-dashboard-kubernetes-dashboard"
service_port = 1337
}
}
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
provider "kubernetes" {
config_path = "~/.kube/config"
config_context = "home-k8s"
}
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
config_context = "home-k8s"
}
}
@@ -0,0 +1,14 @@
# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
resource "null_resource" "metrics_server" {
provisioner "local-exec" {
when = create
command = "kubectl apply -f ./static/metrics-server.yaml"
}
provisioner "local-exec" {
when = destroy
command = "kubectl delete -f ./static/metrics-server.yaml"
}
}
@@ -0,0 +1,14 @@
# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
resource "null_resource" "flannel_networking" {
provisioner "local-exec" {
when = create
command = "kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml"
}
provisioner "local-exec" {
when = destroy
command = "kubectl delete -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml"
}
}
@@ -0,0 +1,12 @@
resource "null_resource" "metallb_networking" {
provisioner "local-exec" {
when = create
command = "kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/main/manifests/metallb.yaml; kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e \"s/strictARP: false/strictARP: true/\" | kubectl apply -f - -n kube-system"
}
provisioner "local-exec" {
when = destroy
command = "kubectl delete -f https://raw.githubusercontent.com/metallb/metallb/main/manifests/metallb.yaml"
}
}
+52
View File
@@ -0,0 +1,52 @@
# Basic networking
module "networking_flannel" {
source = "./modules/networking-flannel"
}
resource "kubernetes_namespace" "metallb_system" {
metadata {
name = "metallb-system"
labels = {
app = "metallb"
}
}
}
module "networking_metallb" {
depends_on = [kubernetes_namespace.metallb_system]
source = "./modules/networking-metallb"
}
resource "random_string" "metallb_secret_string" {
length = 128
special = false
}
resource "kubernetes_secret" "metallb_secret" {
type = "generic"
metadata {
name = "memberlist"
namespace = "metallb-system"
}
data = {
secretkey = base64encode(random_string.metallb_secret_string.result)
}
}
resource "kubernetes_config_map" "metallb_cfg_map" {
metadata {
name = "config"
namespace = "metallb-system"
}
data = {
config = <<CFGMAP
address-pools:
- name: default
protocol: layer2
addresses:
- ${var.network_subnet}.200-${var.network_subnet}.250
CFGMAP
}
}
+65
View File
@@ -0,0 +1,65 @@
# resource "null_resource" "prometheus_pv" {
# provisioner "local-exec" {
# when = create
# command = "kubectl apply -f static/prometheus-pv.yaml"
# }
# provisioner "local-exec" {
# when = destroy
# command = "kubectl delete -f static/prometheus-pv.yaml"
# }
# }
resource "helm_release" "prometheus" {
# depends_on = [ null_resource.prometheus_pv ]
name = "prometheus"
namespace = "default"
chart = "kube-prometheus-stack"
repository = "https://prometheus-community.github.io/helm-charts"
wait = false
# set {
# name = "kube-state-metrics.image.repository"
# value = "carlosedp/kube-state-metrics"
# }
# set {
# name = "kube-state-metrics.image.tag"
# value = "v1.9.6"
# }
}
resource "null_resource" "prometheus_patch" {
depends_on = [helm_release.prometheus]
provisioner "local-exec" {
when = create
command = <<EOF
kubectl patch ds prometheus-prometheus-node-exporter --type json -p '[{"op": "remove", "path" : "/spec/template/spec/containers/0/volumeMounts/2/mountPropagation"}]' || true
EOF
}
}
resource "kubernetes_ingress" "traefik_grafana_routing" {
metadata {
name = "traefik-k8s-grafana"
annotations = {
"kubernetes.io/ingress.class" : "traefik"
}
namespace = "default"
}
spec {
rule {
host = "grafana.local"
http {
path {
path = "/"
backend {
service_name = "prometheus-grafana"
service_port = 80
}
}
}
}
}
}
@@ -0,0 +1,15 @@
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteUDP
metadata:
name: adguard-traefik-udp
namespace: adguard
spec:
entryPoints:
- dnsentry
routes:
- match: HostSNI(`*`)
services:
- name: adguard-fwd-udp
port: 53
protocol: "UDP"
@@ -0,0 +1,188 @@
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
name: system:aggregated-metrics-reader
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
rules:
- apiGroups:
- ""
resources:
- pods
- nodes
- nodes/stats
- namespaces
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server:system:auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:metrics-server
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 0
template:
metadata:
labels:
k8s-app: metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --kubelet-insecure-tls
- --authorization-always-allow-paths=/livez,/readyz
image: k8s.gcr.io/metrics-server/metrics-server:v0.4.2
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 4443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
periodSeconds: 10
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
volumes:
- emptyDir: {}
name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100
@@ -0,0 +1,63 @@
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
finalizers:
- kubernetes.io/pv-protection
name: prometheus-server
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 8Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: prometheus-server
namespace: monitoring
local:
path: /data/localstorage/prometheus-server
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- pi-worker
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
volumeMode: Filesystem
---
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
finalizers:
- kubernetes.io/pv-protection
name: prometheus-alertmanager
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 2Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: prometheus-alertmanager
namespace: monitoring
local:
path: /data/localstorage/prometheus-alertmanager
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- pi-worker
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
volumeMode: Filesystem
@@ -0,0 +1,4 @@
providers:
file:
filename: /custom-data/traefik-config.yaml
kubernetesIngress: {}
@@ -0,0 +1,22 @@
# Activate Pilot integration
pilot:
enabled: true
token: "${traefik_api_key_value}"
additionalArguments:
# - --entryPoints.tcpentry.address=:3333/tcp
# - --entryPoints.udpentry.address=:3333/udp
- --entryPoints.dnsentry.address=:53/udp
- --providers.file.filename=/custom-data/traefik-config.yaml
- --providers.file.watch=true
- --providers.kubernetesingress=true
- --metrics.prometheus=true
- --metrics.prometheus.entryPoint=metrics
- --metrics.prometheus.addServicesLabels=true
- --metrics.prometheus.addEntryPointsLabels=true
- --entryPoints.metrics.address=:8082
volumes:
- mountPath: /custom-data
name: traefik-config
type: configMap
+196
View File
@@ -0,0 +1,196 @@
resource "kubernetes_storage_class" "torrent_nfs" {
metadata {
name = "nfs-torrent"
}
storage_provisioner = "local"
reclaim_policy = "Retain"
allow_volume_expansion = true
volume_binding_mode = "Immediate"
}
resource "kubernetes_namespace" "torrent_ns" {
metadata {
name = "torrent"
labels = {
app = "torrent"
}
}
}
resource "kubernetes_ingress" "torrent_ingress" {
metadata {
name = "traefik-k8s"
annotations = {
"kubernetes.io/ingress.class" : "traefik"
}
namespace = kubernetes_namespace.torrent_ns.metadata.0.name
}
spec {
rule {
host = "torrent.local"
http {
path {
path = "/"
backend {
service_name = kubernetes_service.torrent_svc.metadata.0.name
service_port = 3000
}
}
}
}
}
}
resource "kubernetes_service" "torrent_svc" {
depends_on = [kubernetes_config_map.metallb_cfg_map]
metadata {
namespace = kubernetes_namespace.torrent_ns.metadata.0.name
name = "torrent-fwd"
annotations = {
"metallb.universe.tf/address-pool" : "default"
}
}
spec {
port {
name = "torrent-rpc"
port = 5000
target_port = 5000
}
port {
name = "torrent-tcp"
port = 23340
target_port = 23340
}
port {
name = "torrent-http"
port = 3000
target_port = 3000
}
load_balancer_ip = "${var.network_subnet}.${var.rtorrent_rpc_host}"
selector = {
"app" : "torrent"
}
type = "LoadBalancer"
}
}
resource "kubernetes_persistent_volume" "torrent_shared_nfs" {
metadata {
name = "torrent-pv"
labels = {
name = "type"
value = "local"
}
}
spec {
capacity = {
storage = "8Ti"
}
storage_class_name = kubernetes_storage_class.torrent_nfs.metadata.0.name
access_modes = ["ReadWriteMany"]
persistent_volume_source {
host_path {
path = var.nfs_torrent_path
}
}
}
}
resource "kubernetes_persistent_volume_claim" "torrent_shared_nfs_claim" {
metadata {
name = "torrent-pvc"
namespace = kubernetes_namespace.torrent_ns.metadata.0.name
}
spec {
storage_class_name = kubernetes_storage_class.torrent_nfs.metadata.0.name
access_modes = ["ReadWriteMany"]
resources {
requests = {
storage = "8Ti"
}
}
volume_name = kubernetes_persistent_volume.torrent_shared_nfs.metadata.0.name
}
}
## rTorrent deployment
resource "kubernetes_deployment" "flood-torrent" {
depends_on = [kubernetes_persistent_volume_claim.torrent_shared_nfs_claim]
metadata {
name = "flood-torrent"
labels = {
app = "torrent"
}
namespace = kubernetes_namespace.torrent_ns.metadata.0.name
}
spec {
replicas = 1
selector {
match_labels = {
app = "torrent"
}
}
template {
metadata {
labels = {
app = "torrent"
}
}
spec {
volume {
name = "torrent-v-nfs"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.torrent_shared_nfs_claim.metadata.0.name
}
}
container {
name = "flood-torrent"
image = "ghcr.io/lukaszraczylo/flood:1.2292.7"
env {
name = "FLOOD_DISABLE_AUTH"
value = "true"
}
args = ["--rthost", "${var.network_subnet}.${var.rtorrent_rpc_host}",
"--rtport", "5000", "-e", "HOME=/config", "--auth", "none", "-d", "/config/flood",
"--allowedpath", "/config",
"--allowedpath", "/data",
"--rtorrent", "--rtconfig", "/data/rtorrent/.rtorrent.rc"]
port {
container_port = 3000
protocol = "TCP"
}
port {
container_port = 5000
protocol = "TCP"
}
port {
container_port = 23340
protocol = "TCP"
}
port {
container_port = 23340
protocol = "UDP"
}
volume_mount {
mount_path = "/data"
name = "torrent-v-nfs"
}
volume_mount {
mount_path = "/mnt/storage/torrent/downloaded"
sub_path = "downloaded"
name = "torrent-v-nfs"
}
volume_mount {
mount_path = "/config"
sub_path = "flood"
name = "torrent-v-nfs"
}
}
}
}
}
}
+51
View File
@@ -0,0 +1,51 @@
resource "helm_release" "traefik" {
# depends_on = [ helm_release.prometheus ]
name = "traefik"
namespace = "default"
chart = "traefik/traefik"
set {
name = "deployment.replicas"
value = "2"
}
set {
name = "service.spec.loadBalancerIP"
value = "${var.network_subnet}.234"
}
values = [
templatefile("static/traefik-static-config.tmpl", { traefik_api_key_value = var.traefik_api_key })
]
}
resource "kubernetes_config_map" "traefic_cfg_map" {
metadata {
name = "traefik-config"
namespace = "default"
}
data = {
"traefik-config.yaml" = file("static/traefik-cfg.yaml")
}
}
resource "kubernetes_service" "traefik_svc" {
depends_on = [kubernetes_config_map.metallb_cfg_map, helm_release.traefik]
metadata {
namespace = "default"
name = "traefik-dashboard"
annotations = {
"metallb.universe.tf/address-pool" : "default"
}
}
spec {
port {
port = 80
target_port = 9000
}
selector = {
"app.kubernetes.io/instance" : "traefik"
}
type = "LoadBalancer"
}
}
+9
View File
@@ -0,0 +1,9 @@
variable "network_subnet" {}
variable "rtorrent_rpc_host" {}
variable "nfs_torrent_path" {}
variable "nfs_adguard_path" {}
variable "adguard_host" {}
variable "adguard_catchall_host" {}
variable "traefik_api_key" {}
variable "github_user" {}
variable "github_pat" {}
+30
View File
@@ -0,0 +1,30 @@
terraform {
required_version = ">= 0.13"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0.2"
}
http = {
source = "hashicorp/http"
version = "~> 2.1.0"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.0.3"
}
null = {
source = "hashicorp/null"
version = "~> 3.1.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.1.0"
}
}
}
+11
View File
@@ -0,0 +1,11 @@
# Variables used for barebone kubernetes setup
network_subnet = "192.168.50"
rtorrent_rpc_host = "245"
adguard_host = "240"
adguard_catchall_host = "249"
nfs_torrent_path = "/mnt/drobo-storage/docker-volumes/torrent"
nfs_adguard_path = "/mnt/drobo-storage/docker-volumes/adguard"
# ENV variable: TRAEFIK_API_KEY sets traefik_api_key
# ENV variable: GH_USER, GH_PAT for authentication with private containers