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:
parent
58b6fdc605
commit
f73504c0f2
13
ansible/prod/ansible.cfg
Normal file
13
ansible/prod/ansible.cfg
Normal 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
|
||||
4
ansible/prod/group_vars/all.yml
Normal file
4
ansible/prod/group_vars/all.yml
Normal file
@ -0,0 +1,4 @@
|
||||
# Global variables for prod
|
||||
storagebox_account: "u469968"
|
||||
admin_allowed_cidrs: "127.0.0.1/8"
|
||||
timezone: "Europe/Istanbul"
|
||||
6
ansible/prod/group_vars/prod.yml
Normal file
6
ansible/prod/group_vars/prod.yml
Normal 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 }}"
|
||||
31
ansible/prod/prod-bootstrap.yml
Normal file
31
ansible/prod/prod-bootstrap.yml
Normal 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
5
ansible/requirements.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
collections:
|
||||
- name: ansible.posix
|
||||
- name: community.general
|
||||
- name: community.docker
|
||||
46
ansible/roles/base/tasks/main.yml
Normal file
46
ansible/roles/base/tasks/main.yml
Normal 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 }}"
|
||||
5
ansible/roles/docker/handlers/main.yml
Normal file
5
ansible/roles/docker/handlers/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Restart Docker
|
||||
ansible.builtin.service:
|
||||
name: docker
|
||||
state: restarted
|
||||
47
ansible/roles/docker/tasks/main.yml
Normal file
47
ansible/roles/docker/tasks/main.yml
Normal 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
|
||||
7
ansible/roles/docker/templates/daemon.json.j2
Normal file
7
ansible/roles/docker/templates/daemon.json.j2
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "50m",
|
||||
"max-file": "5"
|
||||
}
|
||||
}
|
||||
15
ansible/roles/hardening/handlers/main.yml
Normal file
15
ansible/roles/hardening/handlers/main.yml
Normal 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
|
||||
70
ansible/roles/hardening/tasks/main.yml
Normal file
70
ansible/roles/hardening/tasks/main.yml
Normal 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(' ') }}"
|
||||
10
ansible/roles/hardening/templates/jail.local.j2
Normal file
10
ansible/roles/hardening/templates/jail.local.j2
Normal 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
|
||||
29
ansible/roles/node_dirs/tasks/main.yml
Normal file
29
ansible/roles/node_dirs/tasks/main.yml
Normal 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']
|
||||
40
ansible/roles/storagebox/tasks/main.yml
Normal file
40
ansible/roles/storagebox/tasks/main.yml
Normal 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'
|
||||
8
ansible/roles/storagebox_ssh_key/tasks/main.yml
Normal file
8
ansible/roles/storagebox_ssh_key/tasks/main.yml
Normal 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"
|
||||
74
ansible/roles/swarm/tasks/main.yml
Normal file
74
ansible/roles/swarm/tasks/main.yml
Normal 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
14
ansible/test/ansible.cfg
Normal 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
|
||||
4
ansible/test/group_vars/all.yml
Normal file
4
ansible/test/group_vars/all.yml
Normal 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"
|
||||
22
ansible/test/group_vars/test-vault.yml
Normal file
22
ansible/test/group_vars/test-vault.yml
Normal 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
|
||||
7
ansible/test/group_vars/test.yml
Normal file
7
ansible/test/group_vars/test.yml
Normal 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
|
||||
@ -1,14 +1,14 @@
|
||||
"all":
|
||||
"children":
|
||||
"app":
|
||||
"hosts":
|
||||
"iklim-app-01":
|
||||
"ansible_host": "167.235.194.61"
|
||||
"ansible_user": "root"
|
||||
"private_ip": "10.10.10.11"
|
||||
"db":
|
||||
"hosts":
|
||||
"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_user": "root"
|
||||
"private_ip": "10.10.10.11"
|
||||
"private_ip": "10.10.20.11"
|
||||
25
ansible/test/test-bootstrap.yml
Normal file
25
ansible/test/test-bootstrap.yml
Normal 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]
|
||||
Loading…
x
Reference in New Issue
Block a user