use self signed cert for internal domain arcodange.lab
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
- name: Install iSCSI client for Longhorn on Raspberry Pi
|
||||
hosts: raspberries:&local
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Install open-iscsi
|
||||
ansible.builtin.apt:
|
||||
name: open-iscsi
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Enable and start iSCSI service
|
||||
ansible.builtin.service:
|
||||
name: iscsid
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Installer cryptsetup
|
||||
ansible.builtin.apt:
|
||||
name: cryptsetup
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Charger le module noyau dm_crypt
|
||||
ansible.builtin.modprobe:
|
||||
name: dm_crypt
|
||||
state: present
|
||||
|
||||
- name: S'assurer que le module dm_crypt est chargé au démarrage
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/modules
|
||||
line: dm_crypt
|
||||
state: present
|
||||
|
||||
- name: Créer dossier longhorn
|
||||
ansible.builtin.file:
|
||||
path: /mnt/arcodange/longhorn
|
||||
state: directory
|
||||
owner: pi
|
||||
group: docker
|
||||
mode: '0774'
|
||||
ignore_errors: true
|
||||
316
ansible/arcodange/factory/playbooks/system/k3s_config.yml
Normal file
316
ansible/arcodange/factory/playbooks/system/k3s_config.yml
Normal file
@@ -0,0 +1,316 @@
|
||||
---
|
||||
|
||||
- name: System K3S
|
||||
hosts: raspberries:&local
|
||||
|
||||
tasks:
|
||||
- name: prepare inventory for k3s external playbook
|
||||
tags: always
|
||||
ansible.builtin.add_host:
|
||||
hostname: "{{ item }}"
|
||||
groups:
|
||||
- k3s_cluster
|
||||
- "{{ ansible_loop.first | ternary('server', 'agent') }}"
|
||||
loop: "{{ groups.raspberries | intersect(groups.local) | sort }}"
|
||||
loop_control:
|
||||
extended: true
|
||||
extended_allitems: false
|
||||
|
||||
- name: how to reach k3s
|
||||
hosts: server
|
||||
tasks:
|
||||
|
||||
- name: setup longhorn for volumes https://docs.k3s.io/helm
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
dest: /var/lib/rancher/k3s/server/manifests/longhorn-install.yaml
|
||||
content: |-
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
metadata:
|
||||
annotations:
|
||||
helmcharts.cattle.io/managed-by: helm-controller
|
||||
finalizers:
|
||||
- wrangler.cattle.io/on-helm-chart-remove
|
||||
generation: 1
|
||||
name: longhorn-install
|
||||
namespace: kube-system
|
||||
spec:
|
||||
version: v1.9.1
|
||||
chart: longhorn
|
||||
repo: https://charts.longhorn.io
|
||||
failurePolicy: abort
|
||||
targetNamespace: longhorn-system
|
||||
createNamespace: true
|
||||
valuesContent: |-
|
||||
defaultSettings:
|
||||
defaultDataPath: /mnt/arcodange/longhorn
|
||||
vars:
|
||||
longhorn_helm_values: {} # https://github.com/longhorn/longhorn/blob/master/chart/values.yaml
|
||||
|
||||
- name: customize k3s traefik configuration https://docs.k3s.io/helm
|
||||
block:
|
||||
- name: Get my public IP
|
||||
community.general.ipify_facts:
|
||||
- become: true
|
||||
ansible.builtin.copy:
|
||||
dest: /var/lib/rancher/k3s/server/manifests/traefik-v3.yaml
|
||||
content: |-
|
||||
apiVersion: v1
|
||||
data:
|
||||
dynamic.yaml: |-
|
||||
{{ traefik_config_yaml | to_nice_yaml | indent( width=4 ) }}
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: traefik-configmap
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
metadata:
|
||||
name: traefik
|
||||
namespace: kube-system
|
||||
spec:
|
||||
repo: https://traefik.github.io/charts
|
||||
chart: traefik
|
||||
version: v37.4.0
|
||||
targetNamespace: kube-system
|
||||
valuesContent: |-
|
||||
{{ traefik_helm_values | to_nice_yaml | indent( width=4 ) }}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: wildcard-arcodange-lab
|
||||
namespace: kube-system
|
||||
spec:
|
||||
secretName: wildcard-arcodange-lab
|
||||
issuerRef:
|
||||
name: step-issuer
|
||||
kind: StepClusterIssuer
|
||||
group: certmanager.step.sm
|
||||
dnsNames:
|
||||
- arcodange.lab
|
||||
- "*.arcodange.lab"
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: TLSStore
|
||||
metadata:
|
||||
name: default
|
||||
namespace: kube-system
|
||||
spec:
|
||||
defaultCertificate:
|
||||
secretName: wildcard-arcodange-lab
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gitea-external
|
||||
namespace: kube-system
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: {{ hostvars[groups.gitea[0]]['preferred_ip'] }}
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
vars:
|
||||
traefik_config_yaml:
|
||||
http:
|
||||
services:
|
||||
gitea:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://{{ hostvars[groups.gitea[0]]['preferred_ip'] }}:3000"
|
||||
# - url: "http://{{ lookup('dig', groups.gitea[0]) }}:3000" # might work again if deactivate rpi wifi
|
||||
routers:
|
||||
dashboard:
|
||||
# rule: Host(`traefik.arcodange.duckdns.org`)
|
||||
rule: Host(`traefik.arcodange.lab`)
|
||||
service: api@internal
|
||||
middlewares:
|
||||
- localIp
|
||||
# tls:
|
||||
# certResolver: letsencrypt
|
||||
# domains:
|
||||
# - main: "arcodange.duckdns.org"
|
||||
# sans:
|
||||
# - "traefik.arcodange.duckdns.org"
|
||||
entryPoints:
|
||||
- websecure
|
||||
- web
|
||||
acme-challenge:
|
||||
rule: Host(`arcodange.duckdns.org`) && PathPrefix(`/.well-known/acme-challenge`)
|
||||
service: acme-http@internal
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
domains:
|
||||
- main: "arcodange.duckdns.org"
|
||||
sans:
|
||||
- "*.arcodange.duckdns.org"
|
||||
entryPoints:
|
||||
- websecure
|
||||
- web
|
||||
gitea:
|
||||
# rule: Host(`gitea.arcodange.duckdns.org`)
|
||||
rule: Host(`gitea.arcodange.lab`)
|
||||
service: gitea
|
||||
middlewares:
|
||||
- localIp
|
||||
# tls:
|
||||
# certResolver: letsencrypt
|
||||
# domains:
|
||||
# - main: "arcodange.duckdns.org"
|
||||
# sans:
|
||||
# - "gitea.arcodange.duckdns.org"
|
||||
entrypoints:
|
||||
- websecure
|
||||
middlewares:
|
||||
localIp:
|
||||
ipAllowList:
|
||||
sourceRange:
|
||||
- "172.16.0.0/12"
|
||||
- "10.42.0.0/16"
|
||||
- "192.168.1.0/24"
|
||||
- "{{ ipify_public_ip }}/32"
|
||||
# - "0.0.0.0/0"
|
||||
# ipStrategy:
|
||||
# depth: 1
|
||||
traefik_helm_values:
|
||||
deployment:
|
||||
kind: "Deployment"
|
||||
initContainers:
|
||||
- name: volume-permissions
|
||||
image: busybox:latest
|
||||
command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json"]
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
# default is https://github.com/traefik/traefik-helm-chart/blob/v25.0.0/traefik/values.yaml <- for v25 (`kubectl describe deployments.apps traefik -n kube-system | grep helm.sh/chart`)
|
||||
# current is https://github.com/traefik/traefik-helm-chart/blob/v37.4.0/traefik/values.yaml
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: 'true' # make predictible choice of node to direct https traffic to this node and avoid NAT/loss of client IP
|
||||
service:
|
||||
spec:
|
||||
externalTrafficPolicy: Local
|
||||
ports:
|
||||
traefik:
|
||||
expose:
|
||||
default: true
|
||||
web:
|
||||
forwardedHeaders:
|
||||
trustedIPs: ["10.42.0.0/16"] #default k3s cidr
|
||||
ingressRoute:
|
||||
dashboard:
|
||||
enabled: true
|
||||
globalArguments: [] # deactivate --global.sendanonymoususage
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: LEGO_DISABLE_CNAME_SUPPORT
|
||||
value: 'true'
|
||||
logs:
|
||||
general:
|
||||
level: INFO
|
||||
# format: json
|
||||
access:
|
||||
enabled: true
|
||||
timezone: Europe/Paris
|
||||
# format: json
|
||||
podSecurityContext:
|
||||
runAsGroup: 65532
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
fsGroup: 65532 # else the persistent volume might be owned by root and be unwriteable
|
||||
persistence:
|
||||
# -- Enable persistence using Persistent Volume Claims
|
||||
# ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
|
||||
# It can be used to store TLS certificates, see `storage` in certResolvers
|
||||
enabled: true
|
||||
name: data
|
||||
# existingClaim: ""
|
||||
accessMode: ReadWriteOnce
|
||||
size: 128Mi
|
||||
storageClass: "longhorn"
|
||||
# volumeName: ""
|
||||
path: /data
|
||||
annotations: {}
|
||||
volumes:
|
||||
- name: traefik-configmap
|
||||
mountPath: /config
|
||||
type: configMap
|
||||
experimental:
|
||||
plugins:
|
||||
crowdsec-bouncer:
|
||||
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin #https://plugins.traefik.io/plugins/6335346ca4caa9ddeffda116/crowdsec-bouncer-traefik-plugin
|
||||
version: v1.3.3
|
||||
additionalArguments:
|
||||
- '--providers.file.filename=/config/dynamic.yaml'
|
||||
- '--providers.kubernetesingress.ingressendpoint.publishedservice=kube-system/traefik'
|
||||
- "--providers.kubernetescrd.allowcrossnamespace=true"
|
||||
- "--providers.kubernetescrd.allowExternalNameServices=true"
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
# for challenge options cf. https://doc.traefik.io/traefik/https/acme/
|
||||
email: arcodange@gmail.com
|
||||
tlsChallenge: true
|
||||
dnsChallenge:
|
||||
# requires env variable DUCKDNS_TOKEN
|
||||
provider: duckdns
|
||||
propagation:
|
||||
delayBeforeChecks: 120
|
||||
disableChecks: true
|
||||
resolvers:
|
||||
- "1.1.1.1:53"
|
||||
- "8.8.8.8:53"
|
||||
httpChallenge:
|
||||
entryPoint: "web"
|
||||
# It has to match the path with a persistent volume
|
||||
storage: /data/acme.json
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: traefik-duckdns-token
|
||||
# MY_TOKEN=<my token (see https://www.duckdns.org/domains)>
|
||||
# kubectl create secret generic traefik-duckdns-token --from-literal="DUCKDNS_TOKEN=$MY_TOKEN" -n kube-system
|
||||
- name: touch manifests/traefik.yaml to trigger update
|
||||
ansible.builtin.file:
|
||||
path: /var/lib/rancher/k3s/server/manifests/traefik-v3.yaml
|
||||
state: touch
|
||||
become: true
|
||||
|
||||
|
||||
# ---
|
||||
|
||||
- name: redeploy traefik
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: delete old traefik deployment
|
||||
kubernetes.core.k8s:
|
||||
api_version: v1
|
||||
name: traefik
|
||||
kind: Deployment
|
||||
namespace: kube-system
|
||||
state: "absent"
|
||||
- name: delete old deployment job so the k3s helm controller redeploy with our new configuration
|
||||
kubernetes.core.k8s:
|
||||
api_version: batch/v1
|
||||
name: helm-install-traefik
|
||||
kind: Job
|
||||
namespace: kube-system
|
||||
state: "absent"
|
||||
- name: get traefik deployment
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: v1
|
||||
name: traefik
|
||||
kind: Deployment
|
||||
namespace: kube-system
|
||||
wait: true
|
||||
register: traefik_deployment
|
||||
- ansible.builtin.debug:
|
||||
var: traefik_deployment
|
||||
27
ansible/arcodange/factory/playbooks/system/k3s_dns.yml
Normal file
27
ansible/arcodange/factory/playbooks/system/k3s_dns.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# https://docs.k3s.io/advanced#coredns-custom-configuration-imports
|
||||
---
|
||||
- name: "Déclarer le ConfigMap coredns-custom pour arcodange.lab"
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
pihole_ip: "192.168.1.201"
|
||||
coredns_namespace: "kube-system"
|
||||
|
||||
tasks:
|
||||
- name: "Créer / mettre à jour le ConfigMap coredns-custom"
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns-custom
|
||||
namespace: "{{ coredns_namespace }}"
|
||||
data:
|
||||
arcodange-lab.server: |
|
||||
arcodange.lab:53 {
|
||||
errors
|
||||
cache 30
|
||||
forward . {{ pihole_ip }}:53
|
||||
}
|
||||
165
ansible/arcodange/factory/playbooks/system/k3s_ssl.yml
Normal file
165
ansible/arcodange/factory/playbooks/system/k3s_ssl.yml
Normal file
@@ -0,0 +1,165 @@
|
||||
---
|
||||
- name: System K3S
|
||||
hosts: raspberries:&local
|
||||
|
||||
tasks:
|
||||
- name: prepare inventory for k3s external playbook
|
||||
tags: always
|
||||
ansible.builtin.add_host:
|
||||
hostname: "{{ item }}"
|
||||
groups:
|
||||
- k3s_cluster
|
||||
- "{{ ansible_loop.first | ternary('server', 'agent') }}"
|
||||
loop: "{{ groups.raspberries | intersect(groups.local) | sort }}"
|
||||
loop_control:
|
||||
extended: true
|
||||
extended_allitems: false
|
||||
|
||||
# =========================
|
||||
# Play 1 — Read step-ca PKI
|
||||
# =========================
|
||||
- name: Collect PKI material from step-ca
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
step_ca_primary: pi1
|
||||
step_ca_user: step
|
||||
step_ca_root: "/home/step/.step/certs/root_ca.crt"
|
||||
tmp_dir: /tmp/step-ca-cert-manager
|
||||
|
||||
tasks:
|
||||
- name: Ensure local temp directory exists
|
||||
file:
|
||||
path: "{{ tmp_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Fetch root CA
|
||||
fetch:
|
||||
src: "{{ step_ca_root }}"
|
||||
dest: "{{ tmp_dir }}/root_ca.crt"
|
||||
flat: true
|
||||
delegate_to: "{{ step_ca_primary }}"
|
||||
become: true
|
||||
become_user: "{{ step_ca_user }}"
|
||||
run_once: true
|
||||
|
||||
- name: Read and decode PKI material
|
||||
slurp:
|
||||
src: "{{ item }}"
|
||||
loop:
|
||||
- "{{ tmp_dir }}/root_ca.crt"
|
||||
register: pki_raw
|
||||
|
||||
- name: Set PKI facts
|
||||
set_fact:
|
||||
root_ca_b64: "{{ (pki_raw.results | selectattr('item','equalto', tmp_dir + '/root_ca.crt') | first).content }}"
|
||||
|
||||
# =========================
|
||||
# Play 2 — Deploy to k3s
|
||||
# =========================
|
||||
- name: Deploy cert-manager and step-ca integration on k3s server
|
||||
hosts: server
|
||||
gather_facts: false
|
||||
become: true
|
||||
|
||||
vars:
|
||||
namespace: cert-manager
|
||||
jwk_provisioner_name: cert-manager
|
||||
jwk_secret_name: step-jwk-password
|
||||
clusterissuer_name: step-ca
|
||||
step_ca_url: "https://ssl-ca.arcodange.lab:8443"
|
||||
cert_manager_version: v1.19.2
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Get cert-manager provisioner info from step-ca
|
||||
command: >
|
||||
step ca provisioner list
|
||||
register: provisioners_json
|
||||
delegate_to: "{{ step_ca_primary }}"
|
||||
become: true
|
||||
become_user: "{{ step_ca_user }}"
|
||||
run_once: true
|
||||
|
||||
- name: Set fact jwk_kid from provisioner
|
||||
set_fact:
|
||||
jwk_kid: >-
|
||||
{{
|
||||
(provisioners_json.stdout | from_json
|
||||
| selectattr('name', 'equalto', jwk_provisioner_name) | list
|
||||
| first).key.kid
|
||||
}}
|
||||
|
||||
- name: Compute PKI checksum
|
||||
set_fact:
|
||||
pki_checksum: >-
|
||||
{{
|
||||
(hostvars['localhost'].root_ca_b64
|
||||
~ jwk_kid
|
||||
~ step_ca_url
|
||||
~ cert_manager_version) | hash('sha256')
|
||||
}}
|
||||
|
||||
- name: Install cert-manager and step-ca via k3s static manifest
|
||||
copy:
|
||||
dest: /var/lib/rancher/k3s/server/manifests/cert-manager-step-ca.yaml
|
||||
mode: "0600"
|
||||
content: |-
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
metadata:
|
||||
name: cert-manager
|
||||
namespace: kube-system
|
||||
annotations:
|
||||
pki.arcodange.lab/checksum: "{{ pki_checksum }}"
|
||||
spec:
|
||||
chart: cert-manager
|
||||
repo: https://charts.jetstack.io
|
||||
version: {{ cert_manager_version }}
|
||||
targetNamespace: cert-manager
|
||||
createNamespace: true
|
||||
valuesContent: |-
|
||||
installCRDs: true
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ jwk_secret_name }}
|
||||
namespace: {{ namespace }}
|
||||
annotations:
|
||||
pki.arcodange.lab/checksum: "{{ pki_checksum }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
password: >-
|
||||
{{ hostvars[step_ca_primary].vault_step_ca_jwk_password }}
|
||||
---
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
metadata:
|
||||
name: step-issuer
|
||||
namespace: kube-system
|
||||
annotations:
|
||||
pki.arcodange.lab/checksum: "{{ pki_checksum }}"
|
||||
spec:
|
||||
chart: step-issuer
|
||||
repo: https://smallstep.github.io/helm-charts
|
||||
version: 1.9.11
|
||||
targetNamespace: {{ namespace }}
|
||||
createNamespace: false
|
||||
valuesContent: |-
|
||||
certManager:
|
||||
namespace: {{ namespace }}
|
||||
stepClusterIssuer:
|
||||
create: true
|
||||
caUrl: "{{ step_ca_url }}"
|
||||
caBundle: "{{ hostvars['localhost'].root_ca_b64 }}"
|
||||
provisioner:
|
||||
name: {{ jwk_provisioner_name }}
|
||||
kid: "{{ jwk_kid }}"
|
||||
passwordRef:
|
||||
name: {{ jwk_secret_name }}
|
||||
namespace: {{ namespace }}
|
||||
key: password
|
||||
|
||||
161
ansible/arcodange/factory/playbooks/system/pki.md
Normal file
161
ansible/arcodange/factory/playbooks/system/pki.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# PKI
|
||||
|
||||
Explications générées par chatgpt pour expliquer le setup de ssl via "step"
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: debug
|
||||
theme: forest
|
||||
---
|
||||
flowchart TB
|
||||
%% PKI
|
||||
subgraph PKI["Step CA / PKI (Pi1)"]
|
||||
style PKI fill:#ffe0b2,stroke:#ff8c00,stroke-width:2px
|
||||
A[Step CA primaire]:::stepCA
|
||||
B[JWK Provisioner]:::jwk
|
||||
C[Root CA]:::root
|
||||
end
|
||||
|
||||
%% Contrôleur Ansible
|
||||
subgraph Controller["Contrôleur Ansible / Mac"]
|
||||
style Controller fill:#e0f7fa,stroke:#00acc1,stroke-width:2px
|
||||
D[Fetch JWK + Root CA]:::ansible
|
||||
E[Secrets K8s: step-jwk, step-root-ca]:::k8sSecret
|
||||
F[ClusterIssuer cert-manager]:::clusterIssuer
|
||||
end
|
||||
|
||||
%% K3s Cluster + Traefik
|
||||
subgraph K3sCluster["K3s Cluster"]
|
||||
style K3sCluster fill:#f1f8e9,stroke:#558b2f,stroke-width:2px
|
||||
T[Traefik Ingress Controller]:::traefik
|
||||
H[Webapp Pods]:::webapp
|
||||
G["Gitea Service (ExternalName → pi2.home:3000)"]:::gitea
|
||||
end
|
||||
|
||||
Users[Clients / Navigateurs]:::clients
|
||||
|
||||
%% Flèches
|
||||
%% PKI → Controller
|
||||
A --> B
|
||||
C --> D
|
||||
B --> D
|
||||
D --> E
|
||||
E --> F
|
||||
|
||||
%% ClusterIssuer → Traefik services
|
||||
F --> H
|
||||
F --> G
|
||||
|
||||
%% Traefik expose tous les services
|
||||
T --> H
|
||||
T --> G
|
||||
Users -->|HTTPS / HTTP| T
|
||||
|
||||
%% PKI direct (optional, for clarity)
|
||||
A -->|Sign initial cert| F
|
||||
|
||||
%% Styling classes
|
||||
classDef stepCA fill:#fff3e0,stroke:#ff6f00,stroke-width:1px
|
||||
classDef jwk fill:#fff9c4,stroke:#fbc02d,stroke-width:1px
|
||||
classDef root fill:#ffe0b2,stroke:#ff8c00,stroke-width:1px
|
||||
classDef ansible fill:#b2ebf2,stroke:#00acc1,stroke-width:1px
|
||||
classDef k8sSecret fill:#b3e5fc,stroke:#0288d1,stroke-width:1px
|
||||
classDef clusterIssuer fill:#81d4fa,stroke:#0277bd,stroke-width:1px
|
||||
classDef gitea fill:#c8e6c9,stroke:#388e3c,stroke-width:1px
|
||||
classDef webapp fill:#a5d6a7,stroke:#2e7d32,stroke-width:1px
|
||||
classDef traefik fill:#ffe082,stroke:#ff8f00,stroke-width:1px
|
||||
classDef clients fill:#eeeeee,stroke:#9e9e9e,stroke-width:1px
|
||||
```
|
||||
|
||||
- 🔵 PKI (Step CA) : la source de confiance. Toutes les certificats HTTPS proviennent de là.
|
||||
- 🔵 JWK Provisioner : autorise cert-manager à demander des certificats automatiquement.
|
||||
- 🟢 Contrôleur Ansible : centralise les clés, crée les Secrets K8s et ClusterIssuer.
|
||||
- 🟢 Secrets & ClusterIssuer : permettent à cert-manager dans K3s de s’authentifier et obtenir des certificats TLS.
|
||||
- 🟢 Webapp Pods : obtiennent leurs certificats via cert-manager et HTTPS fonctionne automatiquement.
|
||||
- 🔵 Gitea : reçoit directement un certificat signé par Step CA, sert HTTPS hors K3s.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% PKI
|
||||
subgraph PKI["Step CA / PKI (Pi1)"]
|
||||
style PKI fill:#ffe0b2,stroke:#ff8c00,stroke-width:2px
|
||||
A[1️⃣ Initialisation Step CA primaire]:::stepCA
|
||||
B[2️⃣ Création JWK Provisioner pour K3s]:::jwk
|
||||
C[Root CA]:::root
|
||||
end
|
||||
|
||||
%% Contrôleur Ansible
|
||||
subgraph Controller["Contrôleur Ansible / Mac"]
|
||||
style Controller fill:#e0f7fa,stroke:#00acc1,stroke-width:2px
|
||||
D[3️⃣ Fetch JWK + Root CA depuis Step CA]:::ansible
|
||||
E[4️⃣ Création / Mise à jour des Secrets K8s]:::k8sSecret
|
||||
F[5️⃣ Création / Mise à jour ClusterIssuer cert-manager]:::clusterIssuer
|
||||
end
|
||||
|
||||
%% K3s Cluster + Traefik
|
||||
subgraph K3sCluster["K3s Cluster"]
|
||||
style K3sCluster fill:#f1f8e9,stroke:#558b2f,stroke-width:2px
|
||||
T[6️⃣ Traefik Ingress Controller]:::traefik
|
||||
H[7️⃣ Webapp Pods]:::webapp
|
||||
G["8️⃣ Gitea Service (ExternalName → pi2.home:3000)"]:::gitea
|
||||
end
|
||||
|
||||
Users[9️⃣ Client Mac / Navigateurs]:::clients
|
||||
|
||||
%% Flux
|
||||
A --> B
|
||||
C --> D
|
||||
B --> D
|
||||
D --> E
|
||||
E --> F
|
||||
F --> H
|
||||
F --> G
|
||||
T --> H
|
||||
T --> G
|
||||
Users -->|HTTPS / HTTP| T
|
||||
|
||||
%% Styling classes
|
||||
classDef stepCA fill:#fff3e0,stroke:#ff6f00,stroke-width:1px
|
||||
classDef jwk fill:#fff9c4,stroke:#fbc02d,stroke-width:1px
|
||||
classDef root fill:#ffe0b2,stroke:#ff8c00,stroke-width:1px
|
||||
classDef ansible fill:#b2ebf2,stroke:#00acc1,stroke-width:1px
|
||||
classDef k8sSecret fill:#b3e5fc,stroke:#0288d1,stroke-width:1px
|
||||
classDef clusterIssuer fill:#81d4fa,stroke:#0277bd,stroke-width:1px
|
||||
classDef gitea fill:#c8e6c9,stroke:#388e3c,stroke-width:1px
|
||||
classDef webapp fill:#a5d6a7,stroke:#2e7d32,stroke-width:1px
|
||||
classDef traefik fill:#ffe082,stroke:#ff8f00,stroke-width:1px
|
||||
classDef clients fill:#eeeeee,stroke:#9e9e9e,stroke-width:1px
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Cluster["Cluster Kubernetes (k3s)"]
|
||||
subgraph CertManager["Cert-Manager"]
|
||||
ClusterIssuer["ClusterIssuer\n(type: smallstep)"]
|
||||
end
|
||||
|
||||
subgraph Traefik["Traefik (Ingress Controller)"]
|
||||
TLSStore["TLSStore\n(Traefik v2+)"]
|
||||
IngressRoute["IngressRoute\n(TLS: my-tls-store)"]
|
||||
end
|
||||
|
||||
subgraph Apps["Applications"]
|
||||
App1[Service: my-app]
|
||||
App2[Service: my-api]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph Smallstep["Smallstep PKI (step-ca)"]
|
||||
StepCA["step-ca\n(CA interne)"]
|
||||
end
|
||||
|
||||
%% Interactions
|
||||
ClusterIssuer -- "1. Demande de certificat\n(CertificateRequest)" --> StepCA
|
||||
StepCA -- "2. Émet un certificat\n(signé par la CA)" --> ClusterIssuer
|
||||
ClusterIssuer -- "3. Stocke le certificat\n(dans un Secret Kubernetes)" --> Secret[(Secret: my-app-tls)]
|
||||
Secret -- "4. Référencé par" --> TLSStore
|
||||
TLSStore -- "5. Fournit le certificat\n(TLS Termination)" --> IngressRoute
|
||||
IngressRoute -- "6. Route le trafic HTTPS\nvers" --> App1
|
||||
IngressRoute -- "6. Route le trafic HTTPS\nvers" --> App2
|
||||
```
|
||||
123
ansible/arcodange/factory/playbooks/system/prepare_disks.yml
Normal file
123
ansible/arcodange/factory/playbooks/system/prepare_disks.yml
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
- name: Préparer automatiquement le disque externe en ext4 avec label
|
||||
hosts: raspberries:&local
|
||||
become: yes
|
||||
vars:
|
||||
mount_point: /mnt/arcodange
|
||||
disk_label: arcodange_500
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Lister toutes les partitions avec labels
|
||||
command: "lsblk -o NAME,LABEL,SIZE,TYPE,MOUNTPOINT -J -b"
|
||||
register: lsblk_info
|
||||
changed_when: false
|
||||
|
||||
- name: Extraire toutes les partitions
|
||||
set_fact:
|
||||
all_partitions: >-
|
||||
{{
|
||||
(lsblk_info.stdout | from_json).blockdevices
|
||||
| map(attribute='children', default=[]) | flatten
|
||||
| selectattr('type', 'equalto', 'part')
|
||||
| list
|
||||
}}
|
||||
|
||||
- name: Rechercher si le label existe déjà
|
||||
set_fact:
|
||||
labeled_partition: >-
|
||||
{{
|
||||
all_partitions
|
||||
| selectattr('label', 'equalto', disk_label)
|
||||
| list | first | default(None)
|
||||
}}
|
||||
|
||||
- name: Choisir une partition candidate (hors disque système mmcblk0)
|
||||
set_fact:
|
||||
target_partition: "{{ (all_partitions | rejectattr('name', 'search', '^mmcblk0')) | sort(attribute='size') | last }}"
|
||||
when: labeled_partition == None
|
||||
|
||||
- name: Définir target_device selon label existant ou partition candidate
|
||||
set_fact:
|
||||
target_device: >-
|
||||
{{
|
||||
'/dev/' + (
|
||||
(labeled_partition.name | default(''))
|
||||
if labeled_partition != None
|
||||
else target_partition.name
|
||||
)
|
||||
}}
|
||||
|
||||
- name: Vérifier si la partition est déjà montée au bon point
|
||||
set_fact:
|
||||
partition_mounted_correctly: >-
|
||||
{{
|
||||
(labeled_partition != None and labeled_partition.mountpoint == mount_point)
|
||||
or (target_partition != None and target_partition.mountpoint == mount_point)
|
||||
}}
|
||||
|
||||
- debug:
|
||||
var: partition_mounted_correctly
|
||||
|
||||
- name: Demander confirmation avant formatage si label inexistant
|
||||
run_once: true
|
||||
when:
|
||||
- labeled_partition == None
|
||||
- not partition_mounted_correctly
|
||||
pause:
|
||||
prompt: |
|
||||
ATTENTION : la partition {{ target_device }} sera FORMATÉE en ext4
|
||||
et recevra le label {{ disk_label }}.
|
||||
Tapez 'oui' pour continuer :
|
||||
register: user_confirm
|
||||
|
||||
- name: Annuler si l'utilisateur n'a pas confirmé
|
||||
fail:
|
||||
msg: "Formatage annulé."
|
||||
when:
|
||||
- labeled_partition == None
|
||||
- not partition_mounted_correctly
|
||||
- user_confirm.user_input | lower != 'oui'
|
||||
|
||||
- name: Démonter la partition si montée ailleurs
|
||||
mount:
|
||||
path: "{{ (labeled_partition.mountpoint if labeled_partition != None else target_partition.mountpoint) }}"
|
||||
state: unmounted
|
||||
when:
|
||||
- not partition_mounted_correctly
|
||||
- (labeled_partition != None and labeled_partition.mountpoint not in [mount_point, '', None])
|
||||
or (target_partition != None and target_partition.mountpoint not in [mount_point, '', None])
|
||||
|
||||
- name: Formater avec label si nécessaire
|
||||
filesystem:
|
||||
fstype: ext4
|
||||
dev: "{{ target_device }}"
|
||||
force: true
|
||||
opts: "-L {{ disk_label }}"
|
||||
when:
|
||||
- not partition_mounted_correctly
|
||||
- labeled_partition == None
|
||||
|
||||
- name: Créer point de montage si absent
|
||||
file:
|
||||
path: "{{ mount_point }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
|
||||
- name: Monter le disque par label (idempotent)
|
||||
mount:
|
||||
path: "{{ mount_point }}"
|
||||
src: "LABEL={{ disk_label }}"
|
||||
fstype: ext4
|
||||
opts: defaults,nofail
|
||||
state: mounted
|
||||
|
||||
- name: Assurer persistance dans fstab
|
||||
mount:
|
||||
path: "{{ mount_point }}"
|
||||
src: "LABEL={{ disk_label }}"
|
||||
fstype: ext4
|
||||
opts: defaults,nofail
|
||||
state: present
|
||||
13
ansible/arcodange/factory/playbooks/system/rpi.yml
Normal file
13
ansible/arcodange/factory/playbooks/system/rpi.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
- name: Raspberry pi general setup
|
||||
hosts: raspberries:&local
|
||||
gather_facts: yes
|
||||
tags: never
|
||||
become: yes
|
||||
|
||||
tasks:
|
||||
|
||||
- name: set hostname
|
||||
ansible.builtin.hostname:
|
||||
name: "{{ inventory_hostname }}"
|
||||
become: yes
|
||||
when: inventory_hostname != ansible_hostname
|
||||
31
ansible/arcodange/factory/playbooks/system/system.yml
Normal file
31
ansible/arcodange/factory/playbooks/system/system.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
|
||||
- name: Setup général des rpis
|
||||
ansible.builtin.import_playbook: rpi.yml
|
||||
|
||||
- name: dns
|
||||
ansible.builtin.import_playbook: ../dns/dns.yml
|
||||
|
||||
- name: ssl
|
||||
ansible.builtin.import_playbook: ../ssl/ssl.yml
|
||||
|
||||
- name: Préparer les disques pour Longhorn
|
||||
ansible.builtin.import_playbook: prepare_disks.yml
|
||||
|
||||
- name: Installer et configurer Docker
|
||||
ansible.builtin.import_playbook: system_docker.yml
|
||||
|
||||
- name: Installer le client iSCSI pour Longhorn
|
||||
ansible.builtin.import_playbook: iscsi_longhorn.yml
|
||||
|
||||
- name: Préparer l'inventaire et installer K3s
|
||||
ansible.builtin.import_playbook: system_k3s.yml
|
||||
|
||||
- name: Configurer K3S Core DNS
|
||||
ansible.builtin.import_playbook: k3s_dns.yml
|
||||
|
||||
- name: Configurer K3S Cert Issuer
|
||||
ansible.builtin.import_playbook: k3s_ssl.yml
|
||||
|
||||
- name: Configurer K3s (kubeconfig, Longhorn, Traefik)
|
||||
ansible.builtin.import_playbook: k3s_config.yml
|
||||
88
ansible/arcodange/factory/playbooks/system/system_docker.yml
Normal file
88
ansible/arcodange/factory/playbooks/system/system_docker.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
- name: System Docker
|
||||
hosts: raspberries:&local
|
||||
gather_facts: yes
|
||||
tags: never
|
||||
become: yes
|
||||
|
||||
pre_tasks:
|
||||
|
||||
- name: Prevent apt source conflict
|
||||
ansible.builtin.file:
|
||||
state: absent
|
||||
path: /etc/apt/sources.list.d/docker.list
|
||||
become: yes
|
||||
|
||||
- name: Install role geerlingguy.docker
|
||||
community.general.ansible_galaxy_install:
|
||||
type: role
|
||||
name: geerlingguy.docker
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- ansible.builtin.debug:
|
||||
var: ansible_facts.machine
|
||||
|
||||
tasks:
|
||||
|
||||
- include_role:
|
||||
name: geerlingguy.docker
|
||||
|
||||
|
||||
- name: Créer le répertoire /etc/docker s'il n'existe pas
|
||||
ansible.builtin.file:
|
||||
path: /etc/docker
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Lire la configuration Docker existante
|
||||
ansible.builtin.command: "cat /etc/docker/daemon.json"
|
||||
register: docker_config_raw
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
when: ansible.facts.stat.exists
|
||||
vars:
|
||||
ansible_facts:
|
||||
stat:
|
||||
exists: "{{ (ansible.builtin.stat.path='/etc/docker/daemon.json').stat.exists }}"
|
||||
|
||||
- name: Initialiser la variable de config Docker
|
||||
ansible.builtin.set_fact:
|
||||
docker_config: {}
|
||||
|
||||
- name: Parser le JSON existant si le fichier existe
|
||||
ansible.builtin.set_fact:
|
||||
docker_config: "{{ docker_config_raw.stdout | from_json }}"
|
||||
when: docker_config_raw.stdout is defined and docker_config_raw.stdout != ""
|
||||
|
||||
- name: Mettre à jour la config du logger
|
||||
ansible.builtin.set_fact:
|
||||
docker_config: >
|
||||
{{ docker_config | combine({
|
||||
'log-driver': 'json-file',
|
||||
'log-opts': {
|
||||
'max-size': '10m',
|
||||
'max-file': '5'
|
||||
}
|
||||
}, recursive=True) }}
|
||||
|
||||
- name: Écrire la configuration mise à jour
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/docker/daemon.json
|
||||
content: "{{ docker_config | to_nice_json(indent=2) }}"
|
||||
mode: '0644'
|
||||
notify: Redémarrer Docker
|
||||
|
||||
handlers:
|
||||
- name: Redémarrer Docker
|
||||
ansible.builtin.service:
|
||||
name: docker
|
||||
state: restarted
|
||||
|
||||
post_tasks:
|
||||
- name: adding existing user '{{ ansible_user }}' to group docker
|
||||
user:
|
||||
name: '{{ ansible_user }}'
|
||||
groups: docker
|
||||
append: yes
|
||||
become: yes
|
||||
63
ansible/arcodange/factory/playbooks/system/system_k3s.yml
Normal file
63
ansible/arcodange/factory/playbooks/system/system_k3s.yml
Normal file
@@ -0,0 +1,63 @@
|
||||
- name: System K3S
|
||||
hosts: raspberries:&local
|
||||
|
||||
tasks:
|
||||
- name: prepare inventory for k3s external playbook
|
||||
tags: always
|
||||
ansible.builtin.add_host:
|
||||
hostname: "{{ item }}"
|
||||
groups:
|
||||
- k3s_cluster
|
||||
- "{{ ansible_loop.first | ternary('server', 'agent') }}"
|
||||
loop: "{{ groups.raspberries | intersect(groups.local) | sort }}"
|
||||
loop_control:
|
||||
extended: true
|
||||
extended_allitems: false
|
||||
|
||||
- name: Install collection k3s.orchestration
|
||||
local_action:
|
||||
module: community.general.ansible_galaxy_install
|
||||
type: collection
|
||||
name: git+https://github.com/k3s-io/k3s-ansible
|
||||
run_once: true
|
||||
|
||||
- name: Install socat for kubectl port forwarding
|
||||
ansible.builtin.apt:
|
||||
name: socat
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: k3s
|
||||
ansible.builtin.import_playbook: k3s.orchestration.site
|
||||
# ansible.builtin.import_playbook: k3s.orchestration.upgrade
|
||||
# ansible.builtin.import_playbook: k3s.orchestration.reset
|
||||
vars:
|
||||
k3s_version: v1.34.3+k3s1
|
||||
extra_server_args: >-
|
||||
--docker --disable traefik
|
||||
--kubelet-arg="container-log-max-files=5"
|
||||
--kubelet-arg="container-log-max-size=10Mi"
|
||||
extra_agent_args: >-
|
||||
--docker
|
||||
--kubelet-arg="container-log-max-files=5"
|
||||
--kubelet-arg="container-log-max-size=10Mi"
|
||||
api_endpoint: "{{ hostvars[groups['server'][0]]['ansible_host'] | default(groups['server'][0]) }}"
|
||||
|
||||
- name: how to reach k3s
|
||||
hosts: server
|
||||
tasks:
|
||||
- name: copy /etc/rancher/k3s/k3s.yaml to ~/.kube/config from the k3s server and replace 127.0.0.1 with the server ip or hostname
|
||||
run_once: true
|
||||
block:
|
||||
- ansible.builtin.fetch:
|
||||
src: /etc/rancher/k3s/k3s.yaml
|
||||
dest: ~/.kube/config
|
||||
flat: true
|
||||
become: true
|
||||
run_once: true
|
||||
- local_action:
|
||||
module: ansible.builtin.replace
|
||||
path: ~/.kube/config
|
||||
regexp: 'server: https://127.0.0.1:6443'
|
||||
replace: 'server: https://{{ ansible_default_ipv4.address }}:6443'
|
||||
Reference in New Issue
Block a user