이 섹션에서는 서버 환경을 초기화하는 작업을 생성하는데, 예를 들어 유지보수 사용자 생성, 초기 사용자의 비밀번호 설정, 그리고 SSH를 통해 Capistrano에서 새로 생성된 EC2 인스턴스에 처음 로그인할 때 서버 호스트 이름 설정 등이 포함됩니다.
그 전에는 호스트네임을 추가하는 여러 방법을 시도해봤지만, 첫 SSH 로그인 후 각 인스턴스별로 HOSTS를 설정할 때 EC2 쪽에 태그를 달아두는 게 더 똑똑할 것 같았고, 인스턴스를 생성할 때 호스트네임의 출처가 될 태그를 먼저 붙이고, 각 인스턴스에 로그인한 후 그 태그를 참조하여 HOST 이름을 설정하는 게 더 현명할 것 같았습니다. 특히 여러 인스턴스를 동시에 설정할 때 호스트네임에 순차적인 번호를 붙이고 싶을 때, 카운터 변수 'ec2.instances.create()'를 사용할 때 카운터 번호를 재사용할 수 있어 쉬웠습니다. 그래서 인스턴스를 생성할 때 태그를 추가하는 방법은 다음과 같습니다.
# 태그 정보
set :host_name, 'deploy-client'
~(생략됨)~
created_instances = []
CNT = 0
while cnt < fetch(:instance_count) do
i = ec2.instances.create(
~(中略)~
)
sleep 10 while i.status == :panding
i.tags['Name'] = [ fetch(:host_name), format("%02d", cnt+1) ].join('-')
created_instances < i.id
cnt += 1
end
~(省略)~
── と、ec2.instances.create()の後でタグを付けてやればOKです1. 이 경우, 이후 작업에서 인스턴스의 호스트 이름으로 이 'Name' 태그의 값을 사용할 것입니다.
갑자기 주제가 빗나갔지만 다시 본론으로 돌아왔습니다. 첫 번째 SSH 작업으로는 지난번에 만든 'init' 작업을 사용합니다. 흐름은 배포 서버에서 새로 생성된 사용자를 위한 키 쌍을 생성하고, 기본 사용자로 SSH에 로그인한 후 유지보수를 위해 새 사용자 계정을 만드는 것입니다. 그 후 공개키 인증을 사용해 사용자를 위한 SSH 설정을 만들고, 기본 사용자에게 sudo 권한을 비공개화하기 위한 비밀번호를 설정하며, 호스트명을 한 번 로그아웃하도록 설정합니다. 먼저, 작업을 설정하기 전에 여러 매개변수를 정의합니다.
# 태그 정보
set :host_name, 'deploy-client'
# 유지보수 사용자 계정
set :user_account, 'deploy-user'
설정 :user_password, '비밀번호'
# 초기 사용자 비밀번호
set :d ef_password, 'PassWord'
# SSH 이후에 sudo를 실행해야 합니다
:p ty, 맞아요
그리고 초기 설정에는 'init' 작업 작업이 있습니다.
desc '새 인스턴스의 활성화 상태 확인'
작업 :체크 하기
created_instances_list = 'CREATED_INSTANCES'
run_locally 그래
ec2 = AWS::EC2.new
AWS.memoize do
시작
테스트 "[ -f ~/#{created_instances_list} ]"
created_instances = 캡처("CD ~; 고양이 #{created_instances_list}").씹어
ci = created_instances.gsub(/(\[|\s|\])/, '').split(',')
target_instances = ec2.instances.select { |i| i.exists? && i.status == :running && ci.include?( i.id) }.map(&:p rivate_ip_address)
만약 target_instances.length == 0이라면,
"생성된 인스턴스 없음"
끝
pkfn = fetch(:p rivate_key_file)
target_instances.each { |var|
Server var, user: 'ec2-user', roles: %w{web app}, ssh_options: { keys: %W(/home/deploy-user/#{pkfn}), forward_agent: true }
}
끝
구조 => e
정보 E
퇴장
끝
끝
끝
끝
작업 :init => :체크 Do
run_locally 그래
# 공개키 인증을 위한 키 쌍 생성
target_dir = '~/.ssh'
if !test "[ -f #{target_dir}/#{fetch(:user_account)}_rsa ]"
if !test "[ -d #{target_dir} ]"
execute "mkdir -m 700 #{target_dir}"
끝
실행 "CD #{target_dir}; ssh-keygen -t rsa -N \"\" -f #{target_dir}/#{fetch(:user_account)}_rsa"
끝
Set :p Ublic_key_content, capture("cat #{target_dir}/#{fetch(:user_account)}_rsa.pub").chomp
Set :new_private_key_path, "#{target_dir}/#{fetch(:user_account)}_rsa"
끝
On roles(:web) do
# 초기 설정 (사용자 생성 시 .ssh 컨테이너 자동 생성)
if!test "[ -d /etc/skel/.ssh/ ]"
실행 :sudo, "mkdir -m 700 /etc/skel/.ssh/; sudo 터치 /etc/skel/.ssh/authorized_keys; sudo chmod 600 /etc/skel/.ssh/authorized_keys"
끝
# 유지보수용 새 사용자 생성
is_user = capture(:sudo, "cut -d: -f1 /etc/passwd").chomp.gsub(/(\r\n)/, ',').split(',')
만약 !is_user.포함? (fetch(:user_account))
execute :sudo, "useradd -G wheel #{fetch(:user_account)}"
실행 :sudo, "echo \"#{fetch(:user_account)}:#{fetch(:user_password)}\" | sudo chpasswd"
끝
# 공개키 인증이 포함된 SSH 신규 사용자 권한 부여
auth_keys = "/home/#{fetch(:user_account)}/.ssh/authorized_keys"
만약 capture(:sudo, "cat #{auth_keys}").chomp != fetch(:p ublic_key_content)
실행 :sudo, "echo \"#{fetch(:p ublic_key_content)}\" | sudo tee #{auth_keys}"
끝
# 새 인스턴스의 호스트네임을 변경하세요
ec2 = AWS::EC2.new
AWS.memoize do
current_private_ip = capture(:sudo, "ifconfig | grep 'inet addr' | cut -d ':' -f 2 | awk 'NR==1 { print $1 }'").chomp
instance_ids = ec2.instances.select { |i| i.exists? && i.status == :running && i.private_ip_address == current_private_ip }.map(&:id)
host_basename = ec2.instances[instance_ids[0].tags['Name']
만약 capture(:sudo, "호스트명").chomp != host_basename.chomp 이면,
실행 :sudo, "echo \"#{host_basename}\" | sudo tee /proc/sys/kernel/hostname"
실행 :sudo, "sed -i 's/\\(^HOSTNAME=\\).*/\\1#{host_basename}/' /etc/sysconfig/network"
exe :sudo, "hostname #{host_basename}"
끝
끝
# 기본 사용자 "ec2-user"의 비밀번호를 설정하고 sudo 권한을 제거하세요
# 유지보수를 위해 신규 사용자에게 sudo 권한을 부여합니다
is_passwd = capture(:sudo, "cut -d: -f1,2 /etc/shadow").chomp.gsub(/(\r\n)/, ',').split(',')
만약 is_passwd.포함한다면? ("ec2-user:!!") 그럼
실행 :sudo, "echo \"ec2-user:#{fetch(:d ef_password)}\" | sudo chpasswd"
끝
exe :sudo, "sed -i s/ec2-user/#{fetch(:user_account)}/g /etc/sudoers.d/cloud-init"
끝
끝
아니요, 저는 Capistrano, Ruby, DSL에 대해 많은 지식을 가지고 있어서 꽤 어려웠습니다. 저는 여러 개의 'sudo' 명령(리디렉션과 파이프)을 사용해 연결하는 데 꽤 중독되어 있었습니다. 예를 들어, 파이프 명령어가 포함된 한 줄 체인의 경우, 예를 들어 'execute :sudo, "mkdir ~/new_dir | chmod a+w ~/new_dir"' 같은 경우, 그 체인의 후속 'chmod'는 sudo 권한이 없었고 'mkdir'만 실행되었기 때문에 'execute :sudo, "mkdir ~/new_dir | sudo chmod a+w ~/new_dir'가 잘 작동했습니다. 또한, '>' 또는 '>>' 리디렉션을 이용해 파일에 표준 출력을 추가하는 과정이 오류로 작동하지 않았고, 'chpasswd'나 'tee'와 같은 명령어로 표준 출력을 받아내는 파이프와 연계해야 했습니다. 또한, 'check'와 'init' 작업에서 'AWS.memoize' 메서드를 사용해 'AWS SDK for Ruby'의 API를 사용해 응답 캐시가 있을 경우(자세한 내용은 이 사이트 참조)를 기록했습니다. 이 메모화를 통해 API 응답이 'check' 작업에 캐시되어 이후 'init' 작업의 성능이 매우 빠릅니다.
이제 과제를 실행해 보겠습니다.
$ 캡 테스트 발사
INFO[a8c8eba0] localhost에서 /usr/bin/env echo -n ["i-e66952e0", "i-054a901c"] > ~/CREATED_INSTANCES
INFO[a8c8eba0] 0.003초 만에 종료, 출구 상태 0(성공).
$ 캡 테스트 init
INFO[d38e8171] /usr/bin/env sudo mkdir -m 700 /etc/skel/.ssh/로 실행 중; sudo touch /etc/skel/.ssh/authorized_keys; sudo chmod 600 /etc/skel/.ssh/authorized_keys 176.34.62.171에서 실행 중입니다.
INFO[d38e8171] 0.045초 만에 종료, 출구 상태 0(성공).
INFO[84cb8ae3] 176.34.62.171에서 /usr/bin/env sudo useradd -G wheel deploy-user 실행 중
INFO[ef747c5e] /usr/bin/env sudo mkdir -m 700 /etc/skel/.ssh/로 실행 중; sudo touch /etc/skel/.ssh/authorized_keys; sudo chmod 600 /etc/skel/.ssh/authorized_keys 176.34.61.82에서 실행 중입니다.
INFO[84cb8ae3] 0.346초 만에 완주했고, 출구 상태 0(성공).
INFO[235d5728] 실행 중 /usr/bin/env sudo echo "deploy-user:password" | sudo chpasswd on 176.34.62.171
INFO[ef747c5e] 0.085초 만에 종료, 출구 상태 0(성공).
INFO[82ead8fe] 176.34.61.82에서 /usr/bin/env sudo useradd -G wheel deploy-user 실행 중
INFO[82ead8fe] 0.073초 만에 완주, 출구 상태 0(성공).
INFO[3fd263c2] /usr/bin/env sudo echo "deploy-user:password" 실행 중 | sudo chpasswd, 176.34.61.82
INFO[235d5728] 0.163초에 완주했고, 출구 상태 0(성공).
INFO[ad2ae489] Running /usr/bin/env sudo echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAABAQDgQ31X0qXm/eHXZCIecjv57C66cZ4ikLdprDhHZs+KV5/vK0B+/47cZCXaT7UEHdI+Bm3jNTPJoRE8iPzpkWB9L5Ks13tB7yJ5DGEIbFRe8d3kTa6Uwrj1HpL+i8hSZ7Bzbc4JvF5YULj9 7NfVAmpNvAqxF1mRWeuzcmevnsVNJ1nF6ePysNjiWmboepWl+MvIJ8xXLYPzrw8mO1kg7WEB0QxqGN5OsVZjjbmEMLliJ+xbOGfxI50FEa+k2445Y3nynBD9krx/1wayurEVn2t8jKWDn6XLSUJ41Ep43QkwFibwtcVBsfDSPIHVm6S3k9RzaAQWpN6qSrUiabk0yAOp deploy-user@devlab-deploy01" | sudo tee /home/deploy-user/.ssh/authorized_keys 176.34.62.171
INFO[3fd263c2] 0.117초 만에 완주했고, 출구 상태 0(성공).
INFO[ad2ae489] 0.037초 만에 완주했고, 출구 상태 0(성공).
INFO[36d81321] Running /usr/bin/env sudo echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAABAQDgQ31X0qXm/eHXZCIecjv57C66cZ4ikLdprDhHZs+KV5/vK0B+/47cZCXaT7UEHdI+Bm3jNTPJoRE8iPzpkWB9L5Ks13tB7yJ5DGEIbFRe8d3kTa6Uwrj1HpL+i8hSZ7Bzbc4JvF5YULjj 97NfVAmpNvAqxF1mRWeuzcmevnsVNJ1nF6ePysNjiWmboepWl+MvIJ8xXLYPzrw8mO1kg7WEB0QxqGN5OsVZjjbmEMLliJ+xbOGfxI50FEa+k2445Y3nynBD9krx/1wayurEVn2t8jKWDn6XLSUJ41Ep43QkwFibwtcVBsfDSPIHVm6S3k9RzaAQWpN6qSrUiabk0yAOp deploy-user@devlab-deploy01" | sudo tee /home/deploy-user/.ssh/authorized_keys 176.34.61.82
INFO[36d81321] 0.044초 만에 완주했고, 출구 상태 0(성공).
INFO[29ccb02a] 실행 중 /usr/bin/env sudo echo "deploy-client-01" | sudo tee /proc/sys/kernel/hostname on 176.34.61.82
INFO[29ccb02a] 0.021초에 완주했고, 출구 상태 0(성공).
INFO[5af43785] /usr/bin/env sudo sed -i 's/\(^HOSTNAME=\).*/\1deploy-client-01/' /etc/sysconfig/network 176.34.61.82에서 실행 중입니다
INFO[5af43785] 0.015초 만에 완주했고, 출구 상태 0(성공).
INFO[17c7a3cc] 176.34.61.82에서 /usr/bin/env sudo hostname deploy-client-01을 실행 중입니다.
INFO[17c7a3cc] 0.015초에 완주했고, 출구 상태 0(성공).
INFO[1a64d176] /usr/bin/env 실행 중 sudo echo "ec2-user:PassWord" | sudo chpasswd, 176.34.61.82
INFO[1a64d176] 0.031초 만에 완주, 출구 상태 0(성공).
INFO[13f0fbe3] 176.34.61.82에서 /usr/bin/env sudo sed -i s/ec2-user/deploy-user/g /etc/sudoers.d/cloud-init 실행 중입니다
INFO[13f0fbe3] 0.015초 만에 종료했고, 출구 상태 0(성공).
INFO[23a6c3a5] 실행 중 /usr/bin/env sudo echo "deploy-client-02" | sudo tee /proc/sys/kernel/hostname on 176.34.62.171
INFO[23a6c3a5] 0.043초에 완주했고, 출구 상태 0(성공).
INFO[d0215821] /usr/bin/env sudo sed -i 's/\(^HOSTNAME=\).*/\1deploy-client-02/' /etc/sysconfig/network 176.34.62.171에서 실행 중입니다
INFO[d0215821] 0.018초 만에 완주했고, 출구 상태 0(성공).
INFO[ce6a2993] 176.34.62.171에서 /usr/bin/env sudo hostname deploy-client-02를 실행 중입니다
INFO[ce6a2993] 0.017초 만에 완주했고, 출구 상태 0(성공).
INFO[071e7b50] /usr/bin/env sudo echo "ec2-user:PassWord" 실행 중 | sudo chpasswd, 176.34.62.171
INFO[071e7b50] 종료 시간은 0.085초, 출구 상태 0(성공).
INFO[28396919] /usr/bin/env sudo sed -i s/ec2-user/deploy-user/g /etc/sudoers.d/cloud-init 176.34.62.171에서 실행 중입니다
INFO[28396919] 0.018초 만에 종료했고, 출구 상태 0(성공).
배포는 성공했습니다. 확인을 위해 명령줄에서 새로 생성된 유지보수 사용자와 SSH로 연결하세요.
$ ssh -i ~/.ssh/deploy-user_rsa deploy-user@176.34.61.82
__| __|_ )
_| ( / 아마존 리눅스 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-ami/2014.03-release-notes/
보안 목적으로 필요한 패키지는 18개 중 8개
"sudo yum update"를 실행하면 모든 업데이트를 적용하세요.
[deploy-user@deploy-client-01 ~]$ sudo su -
[root@deploy-client-01 ~]# su EC2-user
[ec2-user@deploy-client-01 root]$ sudo su -
지역 시스템에서 평소처럼 강의를 받으셨으리라 믿습니다
관리자. 보통 이 세 가지로 귀결됩니다:
#1) 타인의 사생활을 존중하세요.
#2) 타이핑하기 전에 생각하세요.
#3) 큰 힘에는 큰 책임이 따른다.
[sudo] ec2-user 비밀번호:
EC2-user는 Sudoers 파일에 없습니다. 이 사건은 보고될 것입니다.
[ec2-user@deploy-client-01 root]$
안전하게 로그인할 수 있었습니다. 또한, 유지보수 사용자는 비밀번호 없이도 수도(sudo)를 할 수 있고, 기본 사용자인 ec2-user는 수도(sudo) 권한이 박탈되었습니다. 배포는 예상대로 진행되었습니다.
하지만 이 배포 구성에는 약간의 문제가 있습니다. 인스턴스 실행 후 작업 상태가 ':running'이고 실행되었더라도, 상태 검사가 ':initializing' 상태일 경우 init 작업이 중간에 멈추고 처음으로 SSH를 할 수 없습니다. 이 문제는 현재 체크 작업이 인스턴스 상태가 :running인지 여부만 보기 때문입니다.
시간이 길어지고 있으니 여기까지만 하겠습니다. 다음에는 상태 확인을 결정하는 체크 작업을 수정해 보고, 이 init 작업의 사후 처리로 새로 생성된 유지보수 사용자의 SSH 설정을 Capistrano 구성 파일에 적은 뒤, 패키지를 업데이트하는 원래 배포 프로세스를 도입할 예정입니다.
참고 사이트
- 'Name' 태그는 AWS 관리 콘솔에서 인스턴스 목록 이름으로도 사용되므로, 생성한 인스턴스를 더 쉽게 식별할 수 있도록 이 태그를 추가하는 것을 권장합니다. ↩