방화벽 기본 상태 - off
selinux - off
ansible 유저 생성 - bootstrap_ansible_user.sh
#!/bin/bash
set -e
########################################
# Config
########################################
ANSIBLE_USER="ansible"
KEY_PATH="$HOME/.ssh/ansible_id_rsa"
COMMENT="ansible-control-node"
########################################
# Host Groups
########################################
EXTERNAL_HOSTS=(
nginx1
nginx2
was1
was2
)
OPS_HOSTS=(
monitoring
dr_ha
dr2
)
BASTION_HOST=bastion
INTERNAL_HOSTS=(
redis1
redis2
ai_worker
video_storage
ansible_job
db_vip
db_lb_01
db_lb_02
)
DB_HOSTS=(
dba
dbs
dbb
)
########################################
# SSH Key 준비
########################################
echo "🔐 SSH key 확인 중..."
if [ ! -f "$KEY_PATH" ]; then
echo "👉 SSH key 생성"
ssh-keygen -t ed25519 -f "$KEY_PATH" -C "$COMMENT" -N ""
else
echo "✅ SSH key 이미 존재"
fi
########################################
# Functions
########################################
bootstrap_host() {
local host=$1
echo "🚀 [$host] ansible 유저 bootstrap"
ssh root@"$host" bash <<EOF
set -e
# 1. ansible 유저 생성
id ansible &>/dev/null || useradd ansible
# 2. sudo 권한 부여
echo "ansible ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/ansible
chmod 440 /etc/sudoers.d/ansible
# 3. SSH 디렉토리 준비
mkdir -p /home/ansible/.ssh
chmod 700 /home/ansible/.ssh
chown ansible:ansible /home/ansible/.ssh
EOF
# 4. SSH key 복사
ssh-copy-id -i "${KEY_PATH}.pub" ansible@"$host" || {
echo "⚠️ [$host] 키 복사 실패 (이미 존재 / 접근 불가)"
}
echo "✅ [$host] 완료"
echo
}
########################################
# Execute
########################################
echo "==============================="
echo "🔹 External Zone"
echo "==============================="
for host in "${EXTERNAL_HOSTS[@]}"; do
bootstrap_host "$host"
done
echo "==============================="
echo "🔹 OPS / DR Zone"
echo "==============================="
for host in "${OPS_HOSTS[@]}"; do
bootstrap_host "$host"
done
echo "==============================="
echo "🔹 Bastion"
echo "==============================="
bootstrap_host "$BASTION_HOST"
echo "==============================="
echo "🔹 Internal Service Zone"
echo "==============================="
for host in "${INTERNAL_HOSTS[@]}"; do
bootstrap_host "$host"
done
echo "==============================="
echo "🔹 DB Zone"
echo "==============================="
for host in "${DB_HOSTS[@]}"; do
bootstrap_host "$host"
done
echo "🎉 모든 서버에 ansible 유저 bootstrap 완료"
chmod +x bootstrap_ansible_user.sh
./bootstrap_ansible_user.sh
시간 동기화 - Seoul
timedatectl set-timezone Asia/Seoul
ssh key 배포
########################################
# 공통 설정
########################################
Host *
User ansible
IdentityFile ~/.ssh/ansible_id_rsa
ServerAliveInterval 60
ServerAliveCountMax 3
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
########################################
# External Zone (Direct)
########################################
Host nginx1
HostName 172.16.6.51
Host nginx2
HostName 172.16.6.52
Host was1
HostName 172.16.6.53
Host was2
HostName 172.16.6.54
Host monitoring
HostName 172.16.6.57
Host dr_ha
HostName 172.16.6.58
Host dr2
HostName 172.16.6.59
########################################
# Keepalived VIP (Bastion)
########################################
Host bastion
HostName 10.1.1.1
########################################
# Internal Zone (via VIP)
########################################
Host redis1
HostName 10.1.1.5
proxyjump bastion
Host redis2
HostName 10.1.1.10
ProxyJump bastion
Host ai_worker
HostName 10.1.1.20
ProxyJump bastion
Host ansible_job
HostName 10.1.1.30
ProxyJump bastion
Host db_vip
HostName 10.1.1.40
ProxyJump bastion
Host db_lb_01
HostName 10.1.1.41
ProxyJump bastion
Host db_lb_02
HostName 10.1.1.42
ProxyJump bastion
Host dba
HostName 10.1.5.10
Host dbs
HostName 10.1.5.20
Host dbb
HostName 10.1.5.30
b. ansible 서버에서 실행 - key 배포 =⇒ deploy_ssh_key.sh ./deploy_ssh_key.sh
#!/bin/bash
set -e
KEY_PATH="$HOME/.ssh/ansible_id_rsa"
COMMENT="ansible-control-node"
echo "🔐 SSH key 확인 중..."
if [ ! -f "$KEY_PATH" ]; then
echo "👉 SSH key 생성"
ssh-keygen -t ed25519 -f "$KEY_PATH" -C "$COMMENT" -N ""
else
echo "✅ SSH key 이미 존재"
fi
echo
echo "🚀 SSH 키 배포 시작"
echo "==========================="
########################################
# External Zone (Direct)
########################################
EXTERNAL_HOSTS=(
nginx1
nginx2
was1
was2
)
########################################
# OPS / DR Zone (Direct)
########################################
OPS_HOSTS=(
monitoring
dr_ha
dr2
)
########################################
# Bastion
########################################
BASTION_HOST=bastion
########################################
# Internal Service Zone (via Bastion)
########################################
INTERNAL_HOSTS=(
redis1
redis2
ai_worker
video_storage
ansible_job
db_vip
db_lb_01
db_lb_02
)
########################################
# DB Zone (Direct via NIC 10.1.5.x)
########################################
DB_HOSTS=(
dba
dbs
dbb
)
########################################
# Functions
########################################
copy_key() {
local host=$1
echo " → $host"
ssh-copy-id -i "${KEY_PATH}.pub" ansible@"$host" || {
echo " ⚠️ $host 실패 (이미 키 있음 / 서버 꺼짐 / 접근 불가)"
}
}
########################################
# Execute
########################################
echo
echo "🔹 External Zone"
for host in "${EXTERNAL_HOSTS[@]}"; do
copy_key "$host"
done
echo
echo "🔹 OPS / DR Zone"
for host in "${OPS_HOSTS[@]}"; do
copy_key "$host"
done
echo
echo "🔹 Bastion"
copy_key "$BASTION_HOST"
echo
echo "🔹 Internal Service Zone (via Bastion)"
for host in "${INTERNAL_HOSTS[@]}"; do
copy_key "$host"
done
echo
echo "🔹 DB Zone (10.1.5.x direct)"
for host in "${DB_HOSTS[@]}"; do
copy_key "$host"
done
echo
echo "🎉 완료!"
echo "👉 이제 모든 서버에 대해 비밀번호 없이 ssh / ansible 접속 가능"
c. ansible 서버에서 실행 - ansible 로그인 제한 해제 deploy_sudoers.sh
#!/bin/bash
set -e
ANSIBLE_USER="ansible"
SUDOERS_FILE="/etc/sudoers.d/ansible"
echo "🔐 ansible sudoers 설정 (TTY + sudo -S)"
echo "========================================"
# sudo 비밀번호 1회 입력
read -s -p "👉 ansible sudo 비밀번호 입력: " SUDOPASS
echo
echo
########################################
# 대상 서버
########################################
HOSTS=(
nginx1
nginx2
was1
was2
monitoring
dr_ha
dr2
bastion
ai_worker
db_vip
db_lb_01
db_lb_02
video_storage
ansible_job
dba
dbs
dbb
redis1
redis2
)
########################################
# 함수
########################################
setup_sudoers () {
local host=$1
echo "👉 $host"
ssh -tt "${ANSIBLE_USER}@${host}" <<EOF
echo "${SUDOPASS}" | sudo -S bash -c '
echo "ansible ALL=(ALL) NOPASSWD:ALL" > ${SUDOERS_FILE}
chmod 440 ${SUDOERS_FILE}
visudo -cf ${SUDOERS_FILE}
'
exit
EOF
}
########################################
# 실행
########################################
for host in "${HOSTS[@]}"; do
setup_sudoers "$host"
done
echo
echo "🎉 완료!"
echo "👉 이제 ansible sudo 비밀번호 없이 사용 가능"
통신 테스트
ansible all -m ping
ansible internal -m ping
ansible internal_common -m ping
ansible storage -m ping
ansible web -m ping
[ansible@ansible project_ansible]$ tree
.
├── ansible.cfg
├── inventories
│ └── prod
│ ├── group_vars
│ │ ├── ai_processing.yml
│ │ ├── all.yml
│ │ ├── dr.yml
│ │ ├── monitoring.yml
│ │ ├── postgres.yml
│ │ ├── redis.yml
│ │ ├── storage.yml
│ │ ├── was.yml
│ │ └── web.yml
│ ├── hosts.yml
│ └── host_vars
│ ├── ai_worker.yml
│ └── README.md
├── playbooks
│ ├── dr_ha.yml
│ ├── dr_pgbackrest.yml
│ ├── dr.yml
│ ├── grafana.yml
│ ├── loki.yml
│ ├── node_exporter.yml
│ ├── pgsql-etcd.yml
│ ├── pgsql-haproxy.yml
│ ├── pgsql-keepalived.yml
│ ├── pgsql-patroni.yml
│ ├── pgsql-postgresql.yml
│ ├── primary_pgbackrest.yml
│ ├── prometheus.yml
│ ├── promtail.yml
│ ├── reset.yml
│ └── site.yml
├── README.md
├── roles
│ ├── ai_processing
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── files
│ │ ├── handlers
│ │ ├── tasks
│ │ │ ├── cleanup.yml
│ │ │ ├── ffmpeg.yml
│ │ │ ├── layout.yml
│ │ │ ├── main.yml
│ │ │ ├── runtime.yml
│ │ │ ├── scripts.yml
│ │ │ ├── tts.yml
│ │ │ └── worker.yml
│ │ └── templates
│ │ ├── ai-worker.service.j2
│ │ ├── cleanup_ai.sh.j2
│ │ ├── process_video.sh.j2
│ │ ├── redis_worker.sh.j2
│ │ ├── status.json.j2
│ │ └── tts_generate.sh.j2
│ ├── common
│ │ ├── files
│ │ ├── handlers
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ ├── dr
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── files
│ │ ├── handlers
│ │ ├── tasks
│ │ │ ├── config.yml
│ │ │ ├── install.yml
│ │ │ ├── main.yml
│ │ │ ├── restore.yml
│ │ │ └── stanza.yml
│ │ └── templates
│ │ └── pgbackrest.conf.j2
│ ├── etcd
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── etcd.yml.j2
│ ├── fastapi
│ │ ├── files
│ │ ├── handlers
│ │ ├── tasks
│ │ └── templates
│ ├── grafana
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── files
│ │ │ └── dashboards
│ │ │ ├── disk-io-13022.json
│ │ │ ├── linux-system-13978.json
│ │ │ ├── loki-logs.json
│ │ │ ├── network-12693.json
│ │ │ ├── node-exporter-full-1860.json
│ │ │ └── prometheus-stats-3662.json
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── meta
│ │ │ └── main.yml
│ │ ├── README.md
│ │ ├── tasks
│ │ │ └── main.yml
│ │ ├── templates
│ │ │ ├── grafana.ini.j2
│ │ │ └── provisioning
│ │ │ ├── dashboards
│ │ │ │ └── provider.yml.j2
│ │ │ └── datasources
│ │ │ ├── loki.yml.j2
│ │ │ └── prometheus.yml.j2
│ │ ├── tests
│ │ │ ├── inventory
│ │ │ └── test.yml
│ │ └── vars
│ │ └── main.yml
│ ├── haproxy_pgsql
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── haproxy.cfg.j2
│ ├── keepalived
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── keepalived.conf.j2
│ ├── loki
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── meta
│ │ │ └── main.yml
│ │ ├── README.md
│ │ ├── tasks
│ │ │ └── main.yml
│ │ ├── templates
│ │ │ ├── loki.service.j2
│ │ │ └── loki.yml.j2
│ │ ├── tests
│ │ │ ├── inventory
│ │ │ └── test.yml
│ │ └── vars
│ │ └── main.yml
│ ├── monitoring
│ │ ├── files
│ │ ├── handlers
│ │ ├── tasks
│ │ └── templates
│ ├── nginx
│ │ ├── files
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ ├── fastpi.conf.j2
│ │ └── keepalived.conf.j2
│ ├── node_exporter
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── meta
│ │ │ └── main.yml
│ │ ├── README.md
│ │ ├── tasks
│ │ │ └── main.yml
│ │ ├── templates
│ │ │ └── node_exporter.service.j2
│ │ ├── tests
│ │ │ ├── inventory
│ │ │ └── test.yml
│ │ └── vars
│ │ └── main.yml
│ ├── patroni
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── patroni.yml.j2
│ ├── postgresql
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ └── tasks
│ │ └── main.yml
│ ├── prometheus
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── meta
│ │ │ └── main.yml
│ │ ├── README.md
│ │ ├── tasks
│ │ │ └── main.yml
│ │ ├── templates
│ │ │ ├── prometheus.service.j2
│ │ │ └── prometheus.yml.j2
│ │ ├── test_indent.yml
│ │ ├── tests
│ │ │ ├── inventory
│ │ │ └── test.yml
│ │ └── vars
│ │ └── main.yml
│ ├── promtail
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── meta
│ │ │ └── main.yml
│ │ ├── README.md
│ │ ├── tasks
│ │ │ └── main.yml
│ │ ├── templates
│ │ │ ├── promtail.service.j2
│ │ │ └── promtail.yml.j2
│ │ ├── tests
│ │ │ ├── inventory
│ │ │ └── test.yml
│ │ └── vars
│ │ └── main.yml
│ ├── redis
│ │ ├── defaults
│ │ ├── files
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── redis.conf.j2
│ ├── storage
│ │ ├── files
│ │ ├── handlers
│ │ ├── tasks
│ │ └── templates
│ └── was
│ ├── files
│ │ └── app
│ │ ├── main.py
│ │ └── requirements.txt
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ └── was.service.j2
├── scripts
│ ├── backup.sh
│ ├── healthcheck.sh
│ └── restore.sh
└── vault
ansible-inventory -i inventories/prod/hosts.yml --graph
@all:
|--@ungrouped:
|--@web:
| |--nginx1
| |--nginx2
|--@was:
| |--was1
| |--was2
|--@ops:
| |--monitoring
| |--dr_ha
| |--dr_repo
|--@internal:
| |--@internal_common:
| | |--redis1
| | |--redis2
| | |--ai_worker
| |--@vs:
| | |--video_storage
| |--@pgsql_vip:
| | |--db_vip
| |--@pgsql_lb:
| | |--db_lb_01
| | |--db_lb_02
|--@pgsql_db:
| |--dba
| |--dbs
| |--dbb
ansible-galaxy collection install ansible.posix
[defaults]
inventory = inventories/prod/hosts.yml
roles_path = roles
host_key_checking = False
retry_files_enabled = False
forks = 10
interpreter_python = auto_silent
remote_user = ansible
allow_world_readable_tmpfiles = True
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False