이 연재글은 Ansible 알아보기의 1번째 글입니다.

Ansible이란?

Ansible은 Devops도구로서 IT인프라를 쉽게 관리하기 위한 도구입니다. 코드로서 인프라를 구축하기 때문에 infrastructure as code라고 불리기도 합니다. 시스템 구성시 다양한 애플리케이션을 환경에 맞게 설치하고 관리해야 하는데 ansible은 이러한 일련의 작업들을 자동화하는데 도움을 주는 도구입니다.

예를 들자면 기본적인 웹 서비스를 구성하기 위해선 Linux서버에 webserver, db, cache등의 어플리케이션을 설치해야 합니다. 상황에 따라서 관리해야할 애플리케이션 숫자가 더 늘어날수도 줄어들수도 있습니다. 서버 대수가 한두대이면 개별 설치에 별다른 문제는 없습니다. 그러나 성장중인 서비스라면 서버대수는 점점 더 많아질것이고 수동으로 애플리케이션을 관리하는것은 점점 더 힘들어질 것입니다. 특히나 애플리케이션 세팅시 version이나 configuration에 대해서 차이가 발생하거나 실수할 확률도 높아집니다. 즉 같은 환경을 구성하더라도 사람이 하다보니 실수가 발생하거나, 세팅상의 차이가 발생할수 있다는 것입니다.

이러한 점을 보완하기 위해 과거의 고급 개발자들은 환경 구축을 위해 각자의 쉘스크립트를 만들어 사용하기 시작했습니다. 쉘 스크립트는 인프라 구축시 상당 부분에 대한 자동화를 가져다 주었습니다.

그러나 이러한 쉘스크립트 또한 관리하는 시스템이 복잡해질수록 내용이 알아보기 힘들정도로 복잡해졌습니다. 더구나 쉘 스크립트는 같은 환경을 구축하더라도 정해진 규격이 없어 작성자에 따라 천차만별로 다르게 만들어졌고 누군가 만든 쉘스크립트를 고도화 하려면 코드를 이해하기 힘들어 분석에 많은 시간이 필요하게 되었습니다. 물론 쉘스크립트 작성을 위한 문법을 추가적으로 습득해야 하는점은 덤입니다.

이러한 문제들을 해결하기 위해 다양한 도구들이 등장했고 그중 하나가 Ansible입니다. Ansible을 사용하면 다음과 같은 장점이 있습니다.

  • 구성관리가 yaml로 작성되므로 이해하기 쉽습니다. 즉 러닝커브가 낮습니다.
  • 중앙의 Ansible 서버에서 하나의 코드로 다양한 클라이언트의 운영체제를 통합하여 관리 할수 있습니다. 예를 들면 하나의 코드로 centos나 ubuntu에서 동일하게 동작합니다.
  • 클라이언트 시스템에 ansible을 위한 agent를 따로 설치하지 않아도 됩니다. 즉 클라이언트에 ssh로 접속만 가능하면 되므로 ansible을 위한 추가적인 설정에 대한 부담이 없습니다.
  • 여러번 적용하더라도 모든 시스템에 동일한 결과(멱등성)를 만들어 낼 수 있습니다.
  • 기본으로 제공되는 수많은 모듈을 통해 기능을 다양하게 확장 할 수 있습니다.

Vagrant

여러개의 가상 환경을 쉽게 구축하고 관리할 수 있도록 도와주는 도구입니다. 사용자의 요구에 맞게 시스템 자원을 할당, 배치, 배포해 두었다가 필요시 시스템을 사용할 수 있는 상태로 만들어주는 도구로서 프로비저닝( provisioning ) 도구라고도 합니다.

Ansible을 실습하기 위해서는 여러대의 Linux서버가 필요한데 개인이 여러대의 Linux 환경을 구축하는것은 쉽지 않습니다. Vagrant를 이용하면 한대의 로컬 PC에 여러대의 Linux서버를 가상으로 설치하여 실습 환경을 구축할 수 있습니다. 이렇게 한번 구축만 해놓으면 필요시에 빠르게 로드하여 사용하는것이 가능합니다. ( ansible을 서버에서 바로 실습하는 경우는 아래의 Vagrant 설정 과정을 패스하면 됩니다. )

Vagrant를 이용하여 로컬PC에 ansible 테스트 환경을 만들어 보겠습니다. Vagrant를 다음 링크에서 다운받아 설치합니다.

http://vagrantup.com/downloads.html

vagrant 설치 후 아래 플러그인을 커맨드 라인에서 설치합니다.

$ vagrant plugin install vagrant-vbguest

Virtual Box 설치

Vagrant는 가상서버를 쉽게 만들고 관리하기 위해 사용하는 도구이며 실제 가상서버를 구현해줄 애플리케이션은 따로 설치해야 합니다. 이러한 기능을 제공하는 애플리케이션중 대중적으로 많이 사용하는 Virtual Box를 실습에서 사용하겠습니다. 다음 링크에서 Virtual Box를 다운로드 받아 설치합니다.

http://virtualbox.org/wiki/Downloads

가상 서버 생성

테스트를 위해 총 4대의 가상서버를 만들어보겠습니다. 각각의 서버에는 CentOS 최신버전이 설치되도록 Vagrant를 작성합니다. vagrant init 명령을 통해 초기 환경을 구성합니다.

$ mkdir virtual-env
$ cd virtual-env
$ vagrant init
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

기본으로 생성된 Vagrantfile을 열어보면 첫줄을 통해 해당 언어가 ruby기반 언어로 작성 되었음을 알 수 있습니다. 아래로는 Vagrant 사용법과 관련된 내용이 주석으로 작성되어 있습니다. 필요한 부분을 주석 해제하고 사용하면 됩니다.

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.
  config.vm.box = "base"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
end

위의 내용을 아래와 같이 작성합니다.

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

    #ansible-client001
    config.vm.define "ansible-client001" do |cfg|
        cfg.vm.box = "centos/7"
        cfg.vm.provider "virtualbox" do |vb|
            vb.name = "ansible-client001"
        end
        cfg.vm.host_name = "ansible-client001"
        cfg.vm.network "public_network", ip: "192.168.1.1"
        cfg.vm.network "forwarded_port", guest: 22, host: 60001, auto_correct: true, id: "ssh"
        cfg.vm.network "forwarded_port", guest: 80, host: 60081, auto_correct: true
        cfg.vm.synced_folder "../shared_data", "/shared_data", disabled: true
    end

    #ansible-client002
    config.vm.define "ansible-client002" do |cfg|
        cfg.vm.box = "centos/7"
        cfg.vm.provider "virtualbox" do |vb|
            vb.name = "ansible-client002"
        end
        cfg.vm.host_name = "ansible-client002"
        cfg.vm.network "public_network", ip: "192.168.1.2"
        cfg.vm.network "forwarded_port", guest: 3306, host: 13306, auto_correct: true
        cfg.vm.synced_folder "../shared_data", "/shared_data", disabled: true
    end

    #ansible-client003
    config.vm.define "ansible-client003" do |cfg|
        cfg.vm.box = "centos/7"
        cfg.vm.provider "virtualbox" do |vb|
            vb.name = "ansible-client003"
        end
        cfg.vm.host_name = "ansible-client003"
        cfg.vm.network "public_network", ip: "192.168.1.3"
        cfg.vm.network "forwarded_port", guest: 22, host: 60003, auto_correct: true, id: "ssh"
        cfg.vm.network "forwarded_port", guest: 80, host: 60083, auto_correct: true
        cfg.vm.synced_folder "../shared_data", "/shared_data", disabled: true
    end

    #ansible-server
    config.vm.define "ansible-server" do |cfg|
        cfg.vm.box = "centos/7"
        cfg.vm.provider "virtualbox" do |vb|
            vb.name="ansible-server"
        end
        cfg.vm.host_name = "ansible-server"
        cfg.vm.network "public_network", ip: "192.168.1.10"
        cfg.vm.network "forwarded_port", guest: 22, host: 60010, auto_correct: true, id: "ssh"
        cfg.vm.synced_folder "../shared_data", "/shared_data", disabled: true
        cfg.vm.provision "shell", inline: "yum install epel-release -y"
        cfg.vm.provision "shell", inline: "yum install ansible -y"
        cfg.vm.provision "shell", inline: "yum install tree -y"
    end
end

Vagrant.configure(“2”)는 Vagrant 2버전을 사용하겠다는 의미입니다.
클라이언트로 사용할 가상머신 ansible-client001 ~ ansible-client003을 설정합니다. 이름만 다르고 설정이 모두 똑같으므로 한대의 가상서버에 대한 내용만 보면 됩니다. 작업 내용을 해석하면 다음과 같습니다.

virtualbox에 ansible-client00x란 이름으로 가상서버를 생성하고 OS는 centos/7을 설치합니다. 각각의 내부 네트워크주소는 192.168.1.xxx로 설정합니다. forwarded_port는 로컬PC에서 가상 서버에 접근하기 위한 포트 설정으로 가상서버의 22번 포트는 로컬PC의 6000X포트로 접근할 수 있고, 80포트는 로컬PC의 6008X포트로 접근할 수 있도록 설정한 것입니다. cfg.vm.synced_folder는 로컬PC의 특정 디렉토리와 가상머신의 특정 디렉토리를 공유하겠다는 의미입니다. 사용하지 않을 것이므로 disabled: true로 설정합니다.

ansible-server는 ansible 애플리케이션이 설치되어 다수의 클라이언트 서버에 ssh로 명령을 내리는 호스트 서버 역할을 맡게 됩니다. 클라이언트 서버 설정과 거의 비슷하지만 마지막줄에 추가되는 내용이 있습니다. epel 저장소를 최신으로 업데이트 하고 ansible을 설치하라는 명령입니다. -y가 옵션으로 들어가면 설치시 묻는 물음에 y로 답변하게 됩니다. 추가로 설치하는 tree 패키지는 디렉토리 구조를 쉽게 파악할수 있게 구조를 트리형태로 출력해주는 도구입니다.

가상서버 생성

vagrant up 명령으로 Vagrantfile에 작성한 내용을 실행합니다. 중간에 가상머신에서 사용할 네트워크 인터페이스를 호스트 PC의 어떤 하드웨어 어댑터로 설정할지 묻습니다. 대부분 1번이고 상황에 따라 나열된 네트워크 어댑터중 하나를 타이핑 해주면 됩니다.

$ vagrant up
Bringing machine 'ansible-client001' up with 'virtualbox' provider...
Bringing machine 'ansible-client003' up with 'virtualbox' provider...
Bringing machine 'ansible-server' up with 'virtualbox' provider...
==> ansible-client001: Importing base box 'centos/7'...
==> ansible-client001: Matching MAC address for NAT networking...
==> ansible-client001: Checking if box 'centos/7' version '1905.1' is up to date...
==> ansible-client001: Setting the name of the VM: ansible-client001
==> ansible-client001: Clearing any previously set network interfaces...
==> ansible-client001: Available bridged network interfaces:
1) Realtek RTL8723AE Wireless LAN 802.11n PCI-E NIC
2) Hyper-V Virtual Ethernet Adapter
3) Hyper-V Virtual Ethernet Adapter #3
==> ansible-client001: When choosing an interface, it is usually the one that is
==> ansible-client001: being used to connect to the internet.
    ansible-client001: Which interface should the network bridge to? 1

명령을 실행하면 centos를 받아서 설치하고 네트워크를 설정한 후에 추가적인 업데이트를 설치하게 됩니다. 그래서 최초 가상 서버 구축시에는 시간이 오래걸립니다. 완료가 되면 아래와 같이 virtualbox 관리자에 실행중으로 노출됩니다.

로컬PC에서 각각의 가상 서버에 접속하는 방법은 다음과 같습니다.
$ vagrant ssh [가상서버이름]
설정한 값이 올바르게 적용되었는지 ansible-server에 접속하여 확인해 봅니다. ip address가 Vagrant에서 설정한대로 192.168.1.10으로 표시되는것을 확인할 수 있습니다. 또한 ansible version 확인을 통해 최신 ansible이 설치된것도 확인할 수 있습니다. 다른 서버에도 동일한 방식으로 접속하여 확인이 가능합니다.

$ vagrant ssh ansible-server 
$ ip addr
 1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
     inet6 ::1/128 scope host
        valid_lft forever preferred_lft forever
 2: eth0:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
     link/ether 52:54:00:8a:fe:e6 brd ff:ff:ff:ff:ff:ff
     inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
        valid_lft 86116sec preferred_lft 86116sec
     inet6 fe80::5054:ff:fe8a:fee6/64 scope link
        valid_lft forever preferred_lft forever
 3: eth1:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
     link/ether 08:00:27:3e:eb:74 brd ff:ff:ff:ff:ff:ff
     inet 192.168.1.10/24 brd 192.168.1.255 scope global noprefixroute eth1
        valid_lft forever preferred_lft forever
     inet6 fe80::a00:27ff:fe3e:eb74/64 scope link
        valid_lft forever preferred_lft forever
$ ansible --version
 ansible 2.8.4
   config file = /etc/ansible/ansible.cfg
   configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
   ansible python module location = /usr/lib/python2.7/site-packages/ansible
   executable location = /usr/bin/ansible
   python version = 2.7.5 (default, Apr  9 2019, 14:30:50) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] 

ansible-server에 환경 구성하기

ansible-server에는 ansible만 설치된 상태입니다. 드디어 처음으로 ansible을 이용하여 설정을 추가해보겠습니다. ansible에서는 작업 목록을 playbook이라고 부르고 playbook을 실행함으로서 작업이 실행됩니다. playbook은 yaml형식으로 되어있으며 작성한 작업 내용은 다음과 같습니다.

hosts: localhost는 playbook을 실행하는 서버, 즉 ansible-server 자신에게 설정을 적용하라는 의미입니다. gather_facts는 환경값을 저장하는 옵션인데 현재는 사용하지 않으므로 no로 설정합니다.

작업내용은 task에 순서대로 나열합니다. ansible은 작업 클라이언트 정보를 /etc/ansible/hosts 파일에서 읽어오는데 blockinfile 모듈을 통해 hosts파일에 각각의 그룹명과 클라이언트 ip정보를 세팅할 수 있습니다.

https://docs.ansible.com/ansible/latest/modules/blockinfile_module.html

마지막으로 자주 사용하는 ansible 명령어를 bashrc에 단축어로 등록합니다. 설정후에 서버에 재접속하면 ansible 명령을 ans로 ansible-playbook 명령을 anp로 축약하여 사용할 수 있습니다.

# setup-ansible-env.yml
---
- name: Setup for the Ansible's Environment
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Add "/ect/ansible/hosts"
      blockinfile:
        path: /etc/ansible/hosts
        block: |
            [webservers]
            192.168.1.1
            [dbservers]
            192.168.1.2
            [cacheservers]
            192.168.1.3
    - name: Configure Bashrc
      lineinfile:
        path: /home/vagrant/.bashrc
        line: "{{ item }}"
      with_items:
        - "alias ans='ansible'"
        - "alias anp='ansible-playbook'"
        - "alias ang='ansible-galaxy'"

Vagrantfile에 setup-ansible-env.yml 추가

ansible-server에서 직접 yml 파일을 작성하고 ansible-playbook setup-ansible-env.yml을 실행해도 되지만 가상서버가 준비될때 적용될수 있도록 Vagrantfile에 내용을 추가합니다. Vagrantfile에 아래 내용을 추가하면 setup-ansible-env.yml 파일이 ansible-server에 복사되고 playbook이 실행됩니다.

#Vagrantfile
#ansible-client 설정 생략
#ansible-server
    config.vm.define "ansible-server" do |cfg|
        cfg.vm.box = "centos/7"
        cfg.vm.provider "virtualbox" do |vb|
            vb.name="ansible-server"
        end
        cfg.vm.host_name = "ansible-server"
        cfg.vm.network "public_network", ip: "192.168.1.10"
        cfg.vm.network "forwarded_port", guest: 22, host: 60010, auto_correct: true, id: "ssh"
        cfg.vm.synced_folder "../shared_data", "/shared_data", disabled: true
        cfg.vm.provision "shell", inline: "yum install epel-release -y"
        cfg.vm.provision "shell", inline: "yum install ansible -y"
        cfg.vm.provision "file", source: "setup-ansible-env.yml", destination: "setup-ansible-env.yml"
        cfg.vm.provision "shell", inline: "ansible-playbook setup-ansible-env.yml"
    end

Vagrantfile 추가 설정 적용

vagrant provision을 실행하면 vagrant에 추가로 설정한 작업을 가상서버에 적용할 수 있습니다. 아래와 같이 실행하면 ansible-server에 setup-ansible-env.yml 파일이 업로드 되고 playbook 명령을 통해 작업이 실행 됩니다.

$ vagrant provision

설정이 추가된 후 ansible-server에 접속하여 /etc/ansible/hosts 파일을 확인하면 client정보가 추가된것을 확인할 수 있습니다. 또한 ansible과 ansible-playbook 명령도 단축어로 등록된 것을 확인할 수 있습니다.

$ vagrant ssh ansible-server
$ cat /etc/ansible/hosts
...
# BEGIN ANSIBLE MANAGED BLOCK
[webservers]
192.168.1.1
[dbservers]
192.168.1.2
[cacheservers]
192.168.1.3
# END ANSIBLE MANAGED BLOCK
$ ans
Usage: ansible <host-pattern> [options]
Define and run a single task 'playbook' against a set of hosts
$ anp
Usage: ansible-playbook [options] playbook.yml [playbook2 ...]
Runs Ansible playbooks, executing the defined tasks on the targeted hosts.

ansible-server에서 ssh로 ansible-client에 접속하기

ansible-server에서 ansible-client에 인프라를 구축하려면 ssh접속이 되야합니다. 그런데 아래와 같이 ansible-server에서 ssh로 ansible-client에 접속해 보면 Permission denied가 나오면서 접속이 되지 않습니다. OS 초기 세팅이 비밀번호로 ssh 접속하는것에 대하여 차단되어있기 때문입니다.

$ vagrant ssh ansible-server
$ ssh 192.168.1.1
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

ansible-client ssh password 인증 활성화

client서버에 ssh로 접속시 password 인증을 활성화하기 위해 ssh 설정을 수정하고 데몬을 재시작하는 쉘스크립트을 작성하고 Vagrantfile에 내용을 추가합니다. ansible-server에서 playbook명령을 통해 활성화 시키는게 더 좋은 방법이라 생각할수 있지만 현재 상태에서는 클라이언트로 ssh 접속이 안되므로 Vagrant에서 직접 shell 명령어로 처리하도록 할 수밖에 없습니다.

enable_ssh_password_auth.sh

#! /usr/bin/env bash

sed -i -e 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
systemctl restart sshd

Vagrantfile 수정

ansible-client 설정에 위에서 작성한 쉘 스크립트를 추가합니다.

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

    #ansible-client001
    config.vm.define "ansible-client001" do |cfg|
        # 설정생략
        cfg.vm.provision "shell", path: "enable_ssh_password_auth.sh"
    end
    
    #ansible-client002
    config.vm.define "ansible-client002" do |cfg|
        # 설정생략
        cfg.vm.provision "shell", path: "enable_ssh_password_auth.sh"
    end
    
    #ansible-client003
    config.vm.define "ansible-client003" do |cfg|
        # 설정생략
        cfg.vm.provision "shell", path: "enable_ssh_password_auth.sh"
    end
	
    #ansible-server # 설정생략
end

Vagrant 추가 설정 적용

vagrant provision 명령을 실행하여 추가된 설정을 적용합니다. 그리고 나서 ansible-server 에서 다시 ssh로 클라이언트에 접속하면 이번에는 접속이 성공하는 것을 볼 수 있습니다.

$ vagrant provision
$ vagrant ssh ansible-server
$ ssh 192.168.1.1
The authenticity of host '192.168.1.1 (192.168.1.1)' can't be established.
ECDSA key fingerprint is SHA256:4/EKyS8814Hcziy0QYp7UBL7OnCVxvQThlnST3yB5ao.
ECDSA key fingerprint is MD5:c9:93:bd:03:1e:83:73:54:e1:b6:7b:53:ca:09:ef:0a.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.1' (ECDSA) to the list of known hosts.
vagrant@192.168.1.1's password: vagrant
Last login: Tue Oct  1 14:20:29 2019 from 10.0.2.2

ansible-client 접속시 비밀번호 자동 입력

ansible-server에서 클라이언트 서버로 원할하게 작업 명령을 내리기 위해서는 다음 작업이 필요합니다.

  • 클라이언트 서버 SSH 최초 접속시 신뢰할수 있는 대상인지 묻는 물음이 나오지 않도록 처리
  • 클라이언트 서버 SSH 접속시 비밀번호 입력 요청이 나오지 않도록 처리

위의 내용을 처리하기 위한 실제 작업 내용은 다음과 같습니다.

  • ansible-server의 known_hosts 파일에 client-server의 ECDSA key를 등록
  • client-server의 authorized_key 파일에 ansible-server의 공개키를 등록

auto_ssh_connect.yml 작성

hosts: all이라고 지정하면 모든 클라이언트에 적용됩니다. 첫번째 task는 각각의 클라언트에서 ssh-keyscan 명령을 통해 ecdsa 키를 발급받아 ansible-server의 known-hosts 파일에 쓰는 작업입니다. 두번째 task는 ansible-server에서 ssh-keygen 명령을 통해 공개키를 생성하고 해당 키를 client서버의 authorized_key에 등록하는 작업입니다.

---
- name: Automate SSH connections from ansible-server to ansible-client
  hosts: all
  connection: local
  serial: 1
  gather_facts: no
  vars:
      ansible_password: vagrant

  tasks:
      - name: ssh-keyscan for known-hosts
        command: /usr/bin/ssh-keyscan -t ecdsa {{ ansible_host }}
        register: keyscan

      - name: input key
        lineinfile:
            path: ~/.ssh/known_hosts
            line: "{{item}}"
            create: yes
        with_items:
            - "{{keyscan.stdout_lines}}"

      - name: ssh-keygen for authorized_keys
        command: "ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N ''"
        ignore_errors: yes
        run_once: true

      - name: input key for each client
        connection: ssh
        authorized_key:
            user: vagrant
            state: present
            key: "{{lookup('file','~/.ssh/id_rsa.pub')}}"

Vagrantfile 수정

맨 아랫줄에 auto_ssh_connect.yml 내용을 추가합니다. 이때 vagrant 계정으로 적용을 해야하므로 privileged: false를 추가로 세팅합니다.

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
    # client 설정 생략
    
    #ansible-server
    config.vm.define "ansible-server" do |cfg|
        cfg.vm.box = "centos/7"
        cfg.vm.provider "virtualbox" do |vb|
            vb.name="ansible-server"
        end
        cfg.vm.host_name = "ansible-server"
        cfg.vm.network "public_network", ip: "192.168.1.10"
        cfg.vm.network "forwarded_port", guest: 22, host: 60010, auto_correct: true, id: "ssh"
        cfg.vm.synced_folder "../shared_data", "/shared_data", disabled: true
        cfg.vm.provision "shell", inline: "yum install epel-release -y"
        cfg.vm.provision "shell", inline: "yum install ansible -y"
        cfg.vm.provision "file", source: "setup-ansible-env.yml", destination: "setup-ansible-env.yml"
        cfg.vm.provision "shell", inline: "ansible-playbook setup-ansible-env.yml"
        cfg.vm.provision "file", source: "auto_ssh_connect.yml", destination: "auto_ssh_connect.yml"
        cfg.vm.provision "shell", inline: "ansible-playbook auto_ssh_connect.yml", privileged: false
    end
end

Vagrant 추가 설정 적용

vagrant provision 명령으로 변경 사항을 반영한다음 ansible-server에 접속하여 client-server(192.168.1.1 ~ 192.168.1.3)로 ssh 접속을 시도해 봅니다. 이제 신뢰하는 호스트인지 확인하지 않고 암호를 입력하지 않아도 접속되는 것을 확인할 수 있습니다.

$ vagrant provision
$ vagrant ssh ansible-server
$ ssh 192.168.1.1
[vagrant@ansible-client001 ~]$

Ansible module

ansible에서는 타겟서버로 직접 작업 명령을 내릴수 있는 기능이 있는데 module이라고 명명합니다. 간단하게 아래와 같이 타겟 호스트 모두 또는 특정 inventory의 호스트에게만 ping 명령을 내리는것이 가능한데 ansible에서 ping module을 제공하기 때문입니다. ansible은 유용한 기능들을 module로 만들어 제공하고 있으며 아래 링크를 통해 필요한 module을 조회하면 사용법을 볼수 있습니다.

Ansible에서 Module을 제공하는 이유

ansible로 클라이언트 서버에 명령을 내릴때는 두가지 방법을 사용할수 있습니다. 첫번째는 linux명령을 직접 사용하는 방법. 두번째는 ansible이 제공하는 module를 사용하는 방법입니다. 둘다 결과는 동일하지만 ansible이 제공하는 모듈은 멱등성을 보장할 수 있도록 작성되어 있습니다. 따라서 ansible playbook 작성시 되도록 ansible module 기반으로 작성하는 것이 좋습니다.

https://docs.ansible.com/ansible/latest/modules/list_of_all_modules.html

$ vagrant ssh ansible-server
$ ans all -m ping
192.168.1.3 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
192.168.1.2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
192.168.1.1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
$ ans webservers -m ping
192.168.1.1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

Vagrant 가상 서버 종료

가상 서버를 더이상 사용하지 않을경우 아래와 같이 halt 명령으로 VM을 종료할 수 있습니다.
참고로 가상서버를 삭제하고 싶으면 vagrant destroy [가상서버이름] 명령을 사용하면 됩니다.

$ vagrant halt
==> ansible-server: Attempting graceful shutdown of VM...
==> ansible-client003: Attempting graceful shutdown of VM...
==> ansible-client002: Attempting graceful shutdown of VM...
==> ansible-client001: Attempting graceful shutdown of VM...

여기까지 하면 ansible을 실습하기 위한 기본환경이 구축됩니다. 다음장 부터는 생성된 가상 서버에서 ansible을 이용하여 인프라 환경 구축을 진행해보겠습니다.

실습시 사용한 소스는 아래 github에서 확인할 수 있습니다.

https://github.com/codej99/ansible-web-application.git

연재글 이동
[다음글] Ansible을 이용한 시스템 구성관리(2) – ansible로 nginx 설치 – roles, handler, template, vars