Implement: Initial Ansible environment bootstrapping and core roles

This commit introduces the foundational Ansible playbooks, roles, and configurations for automated provisioning of both production and test environments.

Key capabilities include:
-   **Base System Setup:** Common packages, timezone, chrony, and hostname.
-   **Security Hardening:** SELinux disable, SSH configuration, `dnf-automatic`, `fail2ban`, `firewalld` setup, and `journald` log limits.
-   **Docker & Swarm:** Docker installation and configuration, Docker Swarm initialization/joining for managers and workers, overlay network creation, and node labeling.
-   **Storage:** Hetzner StorageBox integration using `davfs2`.
-   **Directory Structure:** Creation of application and database-specific directories.

This establishes a comprehensive, automated pipeline for infrastructure deployment and initial configuration.
This commit is contained in:
Murat ÖZDEMİR 2026-05-11 17:51:43 +03:00
parent 58b6fdc605
commit f73504c0f2
22 changed files with 489 additions and 7 deletions

13
ansible/prod/ansible.cfg Normal file
View File

@ -0,0 +1,13 @@
[defaults]
inventory = inventory/generated/prod.yml
remote_user = root
host_key_checking = False
retry_files_enabled = False
interpreter_python = auto_silent
roles_path = ../roles
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

View File

@ -0,0 +1,4 @@
# Global variables for prod
storagebox_account: "u469968"
admin_allowed_cidrs: "127.0.0.1/8"
timezone: "Europe/Istanbul"

View File

@ -0,0 +1,6 @@
# Prod environment specific variables
storagebox_user: "{{ storagebox_account }}-sub2" # Prod sub-account suffix
storagebox_url: "https://{{ storagebox_user }}.your-storagebox.de/"
storagebox_mount_point: "/mnt/storagebox"
swarm_manager_ip: "10.20.10.11"
# storagebox_password: "{{ vault_storagebox_password }}"

View File

@ -0,0 +1,31 @@
---
- name: Prod Environment Bootstrap (Common Roles)
hosts: all
become: yes
roles:
- role: base
tags: [base]
- role: hardening
tags: [hardening]
- role: docker
tags: [docker]
- role: node_dirs
tags: [node_dirs]
- role: storagebox
tags: [storagebox]
- name: Swarm Infrastructure Setup (Prod HA)
hosts: iklim-app-*
become: yes
serial: 1
roles:
- role: swarm
tags: [swarm]
# Prod'da DB node'ları da worker olarak swarm'a katılır
- name: DB Nodes Swarm Join
hosts: iklim-db-*
become: yes
roles:
- role: swarm
tags: [swarm]

5
ansible/requirements.yml Normal file
View File

@ -0,0 +1,5 @@
---
collections:
- name: ansible.posix
- name: community.general
- name: community.docker

View File

@ -0,0 +1,46 @@
---
- name: Update all packages
ansible.builtin.dnf:
name: "*"
state: latest
update_cache: yes
- name: Install EPEL release
ansible.builtin.dnf:
name: epel-release
state: present
- name: Install base packages
ansible.builtin.dnf:
name:
- curl
- wget
- git
- jq
- tar
- unzip
- bash-completion
- gettext
- tree
- ca-certificates
- fail2ban
- chrony
- python3
- python3-pip
- htop
- btop
state: present
- name: Set timezone
community.general.timezone:
name: "{{ timezone }}"
- name: Ensure chrony is running
ansible.builtin.service:
name: chronyd
state: started
enabled: yes
- name: Set hostname
ansible.builtin.hostname:
name: "{{ inventory_hostname }}"

View File

@ -0,0 +1,5 @@
---
- name: Restart Docker
ansible.builtin.service:
name: docker
state: restarted

View File

@ -0,0 +1,47 @@
---
- name: Add Docker repository
ansible.builtin.get_url:
url: https://download.docker.com/linux/rhel/docker-ce.repo
dest: /etc/yum.repos.d/docker-ce.repo
mode: '0644'
- name: Install Docker packages
ansible.builtin.dnf:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
- name: Ensure /etc/docker directory exists
ansible.builtin.file:
path: /etc/docker
state: directory
mode: '0755'
- name: Configure Docker daemon (Log Rotation)
ansible.builtin.template:
src: daemon.json.j2
dest: /etc/docker/daemon.json
mode: '0644'
notify: Restart Docker
- name: Ensure Docker is started and enabled
ansible.builtin.service:
name: docker
state: started
enabled: yes
- name: Allow Docker traffic in firewalld
ansible.posix.firewalld:
port: "{{ item }}"
permanent: yes
immediate: yes
state: enabled
loop:
- 2377/tcp
- 7946/tcp
- 7946/udp
- 4789/udp

View File

@ -0,0 +1,7 @@
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}

View File

@ -0,0 +1,15 @@
---
- name: Restart sshd
ansible.builtin.service:
name: sshd
state: restarted
- name: Restart fail2ban
ansible.builtin.service:
name: fail2ban
state: restarted
- name: Restart journald
ansible.builtin.service:
name: systemd-journald
state: restarted

View File

@ -0,0 +1,70 @@
---
- name: Disable SELinux
ansible.posix.selinux:
state: disabled
register: selinux_status
- name: Reboot if SELinux changed
ansible.builtin.reboot:
when: selinux_status.changed
- name: Configure SSH Hardening
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- { regexp: "^PasswordAuthentication", line: "PasswordAuthentication no" }
- { regexp: "^PermitRootLogin", line: "PermitRootLogin prohibit-password" }
- { regexp: "^PermitEmptyPasswords", line: "PermitEmptyPasswords no" }
- { regexp: "^MaxAuthTries", line: "MaxAuthTries 3" }
notify: Restart sshd
- name: Install dnf-automatic
ansible.builtin.dnf:
name: dnf-automatic
state: present
- name: Enable dnf-automatic timer
ansible.builtin.systemd:
name: dnf-automatic.timer
state: started
enabled: yes
- name: Configure fail2ban jail
ansible.builtin.template:
src: jail.local.j2
dest: /etc/fail2ban/jail.local
notify: Restart fail2ban
- name: Ensure firewalld is running
ansible.builtin.service:
name: firewalld
state: started
enabled: yes
- name: Configure firewalld default zone
ansible.builtin.shell: firewall-cmd --set-default-zone=drop
when: ansible_facts.services['firewalld.service'].state == 'running'
changed_when: false
- name: Configure journald log limits
ansible.builtin.lineinfile:
path: /etc/systemd/journald.conf
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- { regexp: "^#?MaxRetentionSec=", line: "MaxRetentionSec=7day" }
- { regexp: "^#?SystemMaxUse=", line: "SystemMaxUse=500M" }
notify: Restart journald
- name: Allow SSH in firewalld from admin CIDRs
ansible.posix.firewalld:
service: ssh
source: "{{ item }}"
state: enabled
permanent: yes
immediate: yes
loop: "{{ admin_allowed_cidrs.split(' ') }}"

View File

@ -0,0 +1,10 @@
[DEFAULT]
ignoreip = 127.0.0.1/8 {{ admin_allowed_cidrs }}
bantime = 21600
findtime = 300
maxretry = 5
banaction = iptables-multiport
backend = systemd
[sshd]
enabled = true

View File

@ -0,0 +1,29 @@
---
- name: Create base directory
ansible.builtin.file:
path: /opt/iklimco
state: directory
mode: '0755'
- name: Create app specific directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/iklimco/ssl
- /opt/iklimco/init
- /opt/iklimco/init/postgresql
- /opt/iklimco/init/mongodb
- /opt/iklimco/stacks
when: inventory_hostname in groups['app']
- name: Create db specific directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/iklimco/db
- /opt/iklimco/backup
when: inventory_hostname in groups['db']

View File

@ -0,0 +1,40 @@
---
- name: Install davfs2
ansible.builtin.dnf:
name: davfs2
state: present
- name: Configure davfs2 secrets
ansible.builtin.lineinfile:
path: /etc/davfs2/secrets
line: "{{ storagebox_url }} {{ storagebox_user }} {{ storagebox_password }}"
create: yes
mode: "0600"
owner: root
group: root
- name: Create mount point
ansible.builtin.file:
path: "{{ storagebox_mount_point }}"
state: directory
mode: "0755"
- name: Add fstab entry for StorageBox
ansible.builtin.lineinfile:
path: /etc/fstab
line: "{{ storagebox_url }} {{ storagebox_mount_point }} davfs _netdev,auto,user,rw,uid=root,gid=root 0 0"
state: present
- name: Mount StorageBox
ansible.builtin.mount:
path: "{{ storagebox_mount_point }}"
src: "{{ storagebox_url }}"
fstype: davfs
opts: "_netdev,auto,user,rw,uid=root,gid=root"
state: mounted
- name: Write mount marker
ansible.builtin.copy:
content: "mounted by ansible"
dest: "{{ storagebox_mount_point }}/.mounted_marker"
mode: '0644'

View File

@ -0,0 +1,8 @@
---
- name: Generate SSH key for StorageBox
ansible.builtin.user:
name: root
generate_ssh_key: yes
ssh_key_type: ed25519
ssh_key_file: .ssh/id_ed25519_storagebox
ssh_key_comment: "{{ inventory_hostname }}-storagebox"

View File

@ -0,0 +1,74 @@
---
- name: Check if Swarm is initialized
ansible.builtin.shell: docker info --format '{{.Swarm.LocalNodeState}}'
register: swarm_status
changed_when: false
# 1. İlk Manager'ın (Leader) başlatılması
- name: Initialize Docker Swarm (Leader)
ansible.builtin.shell: >
docker swarm init
--advertise-addr {{ private_ip }}
when:
- inventory_hostname == groups['app'][0]
- swarm_status.stdout != 'active'
register: swarm_init_result
# 2. Join Token'ların alınması (Sadece Leader üzerinden)
- name: Get Swarm Manager Join Token
ansible.builtin.shell: docker swarm join-token manager -q
register: manager_token
delegate_to: "{{ groups['app'][0] }}"
when: inventory_hostname == groups['app'][0]
changed_when: false
- name: Get Swarm Worker Join Token
ansible.builtin.shell: docker swarm join-token worker -q
register: worker_token
delegate_to: "{{ groups['app'][0] }}"
when: inventory_hostname == groups['app'][0]
changed_when: false
# 3. Diğer App sunucularının Manager olarak katılması (Prod HA için)
- name: Join Swarm as Manager
ansible.builtin.shell: >
docker swarm join
--token {{ hostvars[groups['app'][0]]['manager_token']['stdout'] }}
{{ swarm_manager_ip }}:2377
when:
- inventory_hostname in groups['app']
- inventory_hostname != groups['app'][0]
- swarm_status.stdout != 'active'
# 4. DB sunucularının Worker olarak katılması
- name: Join Swarm as Worker
ansible.builtin.shell: >
docker swarm join
--token {{ hostvars[groups['app'][0]]['worker_token']['stdout'] }}
{{ swarm_manager_ip }}:2377
when:
- inventory_hostname in groups['db']
- swarm_status.stdout != 'active'
# 5. Overlay Network oluşturulması (Sadece bir kez Leader üzerinden)
- name: Create iklimco-net overlay network
community.docker.docker_network:
name: iklimco-net
driver: overlay
attachable: yes
state: present
delegate_to: "{{ groups['app'][0] }}"
run_once: true
# 6. Node Etiketleri (Labels)
- name: Label App nodes (service)
ansible.builtin.shell: docker node update --label-add type=service {{ inventory_hostname }}
delegate_to: "{{ groups['app'][0] }}"
when: inventory_hostname in groups['app']
changed_when: false
- name: Label DB nodes (db)
ansible.builtin.shell: docker node update --label-add role=db {{ inventory_hostname }}
delegate_to: "{{ groups['app'][0] }}"
when: inventory_hostname in groups['db']
changed_when: false

14
ansible/test/ansible.cfg Normal file
View File

@ -0,0 +1,14 @@
[defaults]
inventory = inventory/generated/test.yml
remote_user = root
host_key_checking = False
retry_files_enabled = False
interpreter_python = auto_silent
# Üst dizindeki ortak rolleri kullan
roles_path = ../roles
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

View File

@ -0,0 +1,4 @@
# Global variables for all environments
storagebox_account: "u469968"
admin_allowed_cidrs: "127.0.0.1/8" # Overridden in inventory or vault
timezone: "Europe/Istanbul"

View File

@ -0,0 +1,22 @@
$ANSIBLE_VAULT;1.1;AES256
39313733646339343230326361633435636632393938663537396530393131363335326664346334
3533366238616262366665373638373030393536383962390a626532373431336632366264356261
31336533663537303964613862336530363335616334313839363333383863323462376135636134
3963356335393733650a393439336365343132373038393362653136353462646636396430376561
66633161633434326265376631353734323661643830386437303631386438336536646538326465
35653864633631656461313235316637383063656164353536336634373663353466346161623731
38393365313439623261363732393333376266336663303565373866643135396437356339643136
34303735336365353930353065343234373032363063356133393436383636313038643934663435
61366635396363613537396563613235303665363230656366353739656364376636356433333766
34323464343438356262363337303937646561366366386233353338333434633333373464373234
31323763303366363239353537343966316439656134663033653965613635393562363663323962
34646238386232616464343162386164626638306439346138336263386537653536336130616638
39306366663164373235373863366237313933373633613464353364643630386666336134616364
64363633306465323831623831323139373931393938623233636536636664353839643866393138
37313261623737346433653535393835356635353662386632373964613832333434303739396164
61653438326261346464316230656262393466643939636335363662383466616363333265303536
61663337393038356165316261323035383361666266333665346363623166333434383166653936
34396636373638656633643135316566663736363931393633393365343161636239306535623935
38623165393963383131616261383539643234343064306366663434333166353131333431343532
36363362303131373165646666343938663964323063643363303131336462386431396431323162
34643539326266333236656130616134616663373966613464663136386239303861

View File

@ -0,0 +1,7 @@
# Test environment specific variables
storagebox_user: "{{ storagebox_account }}-sub4"
storagebox_url: "https://{{ storagebox_user }}.your-storagebox.de/"
storagebox_mount_point: "/mnt/storagebox"
swarm_manager_ip: "10.10.10.11"
admin_allowed_cidrs: "78.187.87.109/32 95.70.151.248/32"
# storagebox_password: "{{ vault_storagebox_password }}" # In test-vault.yml

View File

@ -1,14 +1,14 @@
"all": "all":
"children": "children":
"app":
"hosts":
"iklim-app-01":
"ansible_host": "167.235.194.61"
"ansible_user": "root"
"private_ip": "10.10.10.11"
"db": "db":
"hosts": "hosts":
"iklim-db-01": "iklim-db-01":
"ansible_host": "167.235.194.61"
"ansible_user": "root"
"private_ip": "10.10.20.11"
"swarm":
"hosts":
"iklim-app-01":
"ansible_host": "167.235.205.93" "ansible_host": "167.235.205.93"
"ansible_user": "root" "ansible_user": "root"
"private_ip": "10.10.10.11" "private_ip": "10.10.20.11"

View File

@ -0,0 +1,25 @@
---
- name: Test Environment Bootstrap (Common Roles)
hosts: all
become: yes
roles:
- role: base
tags: [base]
- role: hardening
tags: [hardening]
- role: docker
tags: [docker]
- role: node_dirs
tags: [node_dirs]
- role: storagebox
tags: [storagebox]
- role: storagebox_ssh_key
tags: [storagebox_ssh_key]
- name: Swarm Infrastructure Setup
hosts: all
become: yes
serial: 1 # Manager'in önce bitmesi ve token'i worker'a vermesi için
roles:
- role: swarm
tags: [swarm]