Capistranoで新規作成したEC2インスタンスの初期設定
本項では、Capistranoで新規作成したEC2インスタンスへSSHで初回ログインした際の、保守用ユーザの作成、初期ユーザに対してのパスワード設定、サーバのホスト名設定など、いわゆるサーバ環境の初期設定を行うタスクを作ってみます。
その前に、ホスト名のつけ方としての色々と試してみての所感なのですが、初回SSHログイン後にそれぞれのインスタンスに対してHOSTS設定する際にEC2側にタグ付けてしていってもできるのですが、まずインスタンス作成する時にあらかじめホスト名の元となるタグを付けておいて、各インスタンスログイン後はそのタグを参照してHOST名を設定する方がスマートだと思いました。特に複数インスタンスを同時に立てる時などにホスト名に連番を振りたいとかいう時は、カウンター変数を使って回しているec2.instances.create()
時にそのカウンターの数値を転用できるので簡単でした。ということで、インスタンス作成時にタグを追加する方法ですが、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# タグ情報 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権限を剥奪、ホスト名を設定して一旦ログアウトしています。
まず、タスク設定前に各種パラメータを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# タグ情報 set :host_name, 'deploy-client' # 保守ユーザアカウント set :user_account, 'deploy-user' set :user_password, 'password' # 初期ユーザパスワード set :def_password, 'PassWord' # SSH後にsudoを実行するために必要 set :pty, true |
そして、初期設定用のタスクinit
です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
desc 'Check the activation status of new instances' task :check do created_instances_list = 'CREATED_INSTANCES' run_locally do ec2 = AWS::EC2.new AWS.memoize do begin if test "[ -f ~/#{created_instances_list} ]" created_instances = capture("cd ~; cat #{created_instances_list}").chomp ci = created_instances.gsub(/(\[|\s|\])/, '').split(',') target_instances = ec2.instances.select { |i| i.exists? && i.status == :running && ci.include?(i.id) }.map(&:private_ip_address) if target_instances.length == 0 then raise "No created instances" end pkfn = fetch(:private_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 } } end rescue => e info e exit end end end end task :init => :check do run_locally do # 公開鍵認証用のキーペアを作成する target_dir = '~/.ssh' if !test "[ -f #{target_dir}/#{fetch(:user_account)}_rsa ]" if !test "[ -d #{target_dir} ]" execute "mkdir -m 700 #{target_dir}" end execute "cd #{target_dir}; ssh-keygen -t rsa -N \"\" -f #{target_dir}/#{fetch(:user_account)}_rsa" end set :public_key_content, capture("cat #{target_dir}/#{fetch(:user_account)}_rsa.pub").chomp set :new_private_key_path, "#{target_dir}/#{fetch(:user_account)}_rsa" end on roles(:web) do # 初期設定(ユーザ作成時に.sshコンテナを自動作成する) if !test "[ -d /etc/skel/.ssh/ ]" execute :sudo, "mkdir -m 700 /etc/skel/.ssh/; sudo touch /etc/skel/.ssh/authorized_keys; sudo chmod 600 /etc/skel/.ssh/authorized_keys" end # 保守用の新規ユーザを作成 is_user = capture(:sudo, "cut -d: -f1 /etc/passwd").chomp.gsub(/(\r\n)/, ',').split(',') if !is_user.include?(fetch(:user_account)) then execute :sudo, "useradd -G wheel #{fetch(:user_account)}" execute :sudo, "echo \"#{fetch(:user_account)}:#{fetch(:user_password)}\" | sudo chpasswd" end # 新規ユーザに公開鍵認証によるSSH権限を与える auth_keys = "/home/#{fetch(:user_account)}/.ssh/authorized_keys" if capture(:sudo, "cat #{auth_keys}").chomp != fetch(:public_key_content) then execute :sudo, "echo \"#{fetch(:public_key_content)}\" | sudo tee #{auth_keys}" end # 新インスタンスのホスト名を変更 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'] if capture(:sudo, "hostname").chomp != host_basename.chomp then execute :sudo, "echo \"#{host_basename}\" | sudo tee /proc/sys/kernel/hostname" execute :sudo, "sed -i 's/\\(^HOSTNAME=\\).*/\\1#{host_basename}/' /etc/sysconfig/network" execute :sudo, "hostname #{host_basename}" end end # デフォルトユーザ「ec2-user」にパスワードを設定し、sudo権限を剥奪 # 保守用の新規ユーザにsudo権限を付与 is_passwd = capture(:sudo, "cut -d: -f1,2 /etc/shadow").chomp.gsub(/(\r\n)/, ',').split(',') if is_passwd.include?("ec2-user:!!") then execute :sudo, "echo \"ec2-user:#{fetch(:def_password)}\" | sudo chpasswd" end execute :sudo, "sed -i s/ec2-user/#{fetch(:user_account)}/g /etc/sudoers.d/cloud-init" end end |
いやはや、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 SDK for Ruby」でAPIを使っているところのレスポンスについてキャッシュがあればそれを利用するようにAWS.memoize
メソッドでメモ化してみました(詳しくはこちらのサイト)。このメモ化を行うことで、check
タスクでAPIレスポンスがキャッシュされるので、後続のinit
タスクのパフォーマンスが抜群に早くなりました。
では、タスクを実行してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
$ cap test launch INFO[a8c8eba0] Running /usr/bin/env echo -n ["i-e66952e0", "i-054a901c"] > ~/CREATED_INSTANCES on localhost INFO[a8c8eba0] Finished in 0.003 seconds with exit status 0 (successful). $ cap test init INFO[d38e8171] Running /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 on 176.34.62.171 INFO[d38e8171] Finished in 0.045 seconds with exit status 0 (successful). INFO[84cb8ae3] Running /usr/bin/env sudo useradd -G wheel deploy-user on 176.34.62.171 INFO[ef747c5e] Running /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 on 176.34.61.82 INFO[84cb8ae3] Finished in 0.346 seconds with exit status 0 (successful). INFO[235d5728] Running /usr/bin/env sudo echo "deploy-user:password" | sudo chpasswd on 176.34.62.171 INFO[ef747c5e] Finished in 0.085 seconds with exit status 0 (successful). INFO[82ead8fe] Running /usr/bin/env sudo useradd -G wheel deploy-user on 176.34.61.82 INFO[82ead8fe] Finished in 0.073 seconds with exit status 0 (successful). INFO[3fd263c2] Running /usr/bin/env sudo echo "deploy-user:password" | sudo chpasswd on 176.34.61.82 INFO[235d5728] Finished in 0.163 seconds with exit status 0 (successful). INFO[ad2ae489] Running /usr/bin/env sudo echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDgQ31X0qXm/eHXZCIecjv57C66cZ4ikLdprDhHZs+KV5/vK0B+/47cZCXaT7UEHdI+Bm3jNTPJoRE8iPzpkWB9L5Ks13tB7yJ5DGEIbFRe8d3kTa6Uwrj1HpL+i8hSZ7Bzbc4JvF5YULj97NfVAmpNvAqxF1mRWeuzcmevnsVNJ1nF6ePysNjiWmboepWl+MvIJ8xXLYPzrw8mO1kg7WEB0QxqGN5OsVZjjbmEMLliJ+xbOGfxI50FEa+k2445Y3nynBD9krx/1wayurEVn2t8jKWDn6XLSUJ41Ep43QkwFibwtcVBsfDSPIHVm6S3k9RzaAQWpN6qSrUiabk0yAOp deploy-user@devlab-deploy01" | sudo tee /home/deploy-user/.ssh/authorized_keys on 176.34.62.171 INFO[3fd263c2] Finished in 0.117 seconds with exit status 0 (successful). INFO[ad2ae489] Finished in 0.037 seconds with exit status 0 (successful). INFO[36d81321] Running /usr/bin/env sudo echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDgQ31X0qXm/eHXZCIecjv57C66cZ4ikLdprDhHZs+KV5/vK0B+/47cZCXaT7UEHdI+Bm3jNTPJoRE8iPzpkWB9L5Ks13tB7yJ5DGEIbFRe8d3kTa6Uwrj1HpL+i8hSZ7Bzbc4JvF5YULj97NfVAmpNvAqxF1mRWeuzcmevnsVNJ1nF6ePysNjiWmboepWl+MvIJ8xXLYPzrw8mO1kg7WEB0QxqGN5OsVZjjbmEMLliJ+xbOGfxI50FEa+k2445Y3nynBD9krx/1wayurEVn2t8jKWDn6XLSUJ41Ep43QkwFibwtcVBsfDSPIHVm6S3k9RzaAQWpN6qSrUiabk0yAOp deploy-user@devlab-deploy01" | sudo tee /home/deploy-user/.ssh/authorized_keys on 176.34.61.82 INFO[36d81321] Finished in 0.044 seconds with exit status 0 (successful). INFO[29ccb02a] Running /usr/bin/env sudo echo "deploy-client-01" | sudo tee /proc/sys/kernel/hostname on 176.34.61.82 INFO[29ccb02a] Finished in 0.021 seconds with exit status 0 (successful). INFO[5af43785] Running /usr/bin/env sudo sed -i 's/\(^HOSTNAME=\).*/\1deploy-client-01/' /etc/sysconfig/network on 176.34.61.82 INFO[5af43785] Finished in 0.015 seconds with exit status 0 (successful). INFO[17c7a3cc] Running /usr/bin/env sudo hostname deploy-client-01 on 176.34.61.82 INFO[17c7a3cc] Finished in 0.015 seconds with exit status 0 (successful). INFO[1a64d176] Running /usr/bin/env sudo echo "ec2-user:PassWord" | sudo chpasswd on 176.34.61.82 INFO[1a64d176] Finished in 0.031 seconds with exit status 0 (successful). INFO[13f0fbe3] Running /usr/bin/env sudo sed -i s/ec2-user/deploy-user/g /etc/sudoers.d/cloud-init on 176.34.61.82 INFO[13f0fbe3] Finished in 0.015 seconds with exit status 0 (successful). INFO[23a6c3a5] Running /usr/bin/env sudo echo "deploy-client-02" | sudo tee /proc/sys/kernel/hostname on 176.34.62.171 INFO[23a6c3a5] Finished in 0.043 seconds with exit status 0 (successful). INFO[d0215821] Running /usr/bin/env sudo sed -i 's/\(^HOSTNAME=\).*/\1deploy-client-02/' /etc/sysconfig/network on 176.34.62.171 INFO[d0215821] Finished in 0.018 seconds with exit status 0 (successful). INFO[ce6a2993] Running /usr/bin/env sudo hostname deploy-client-02 on 176.34.62.171 INFO[ce6a2993] Finished in 0.017 seconds with exit status 0 (successful). INFO[071e7b50] Running /usr/bin/env sudo echo "ec2-user:PassWord" | sudo chpasswd on 176.34.62.171 INFO[071e7b50] Finished in 0.085 seconds with exit status 0 (successful). INFO[28396919] Running /usr/bin/env sudo sed -i s/ec2-user/deploy-user/g /etc/sudoers.d/cloud-init on 176.34.62.171 INFO[28396919] Finished in 0.018 seconds with exit status 0 (successful). |
デプロイ成功です。
確認のため、コマンドラインから新しく作られた保守用ユーザでSSHログインしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$ ssh -i ~/.ssh/deploy-user_rsa deploy-user@176.34.61.82 __| __|_ ) _| ( / Amazon Linux AMI ___|\___|___| https://aws.amazon.com/amazon-linux-ami/2014.03-release-notes/ 8 package(s) needed for security, out of 18 available Run "sudo yum update" to apply all updates. [deploy-user@deploy-client-01 ~]$ sudo su - [root@deploy-client-01 ~]# su ec2-user [ec2-user@deploy-client-01 root]$ sudo su - We trust you have received the usual lecture from the local System Administrator. It usually boils down to these three things: #1) Respect the privacy of others. #2) Think before you type. #3) With great power comes great responsibility. [sudo] password for ec2-user: ec2-user is not in the sudoers file. This incident will be reported. [ec2-user@deploy-client-01 root]$ |
無事、ログインできました。
さらに、保守用ユーザはパスワードなしでsudoでき、一方、デフォルトユーザのec2-userはsudo権限が剥奪されています。期待通りのデプロイができています。
ただ、このデプロイ設定にはちょっと問題があって、インスタンスラウンチ用のタスク後のインスタンス状態(Instance State)が:running
になって起動済みでも、ステータス確認(Status Checks)が:initializing
状態だと、初回SSHが出来ずにinitタスクが途中で停止してしまいます。現状のcheckタスクではInstance Stateが:runningかどうかしか見ていないための問題ですね。
長くなってきたので、今回はここまでとします。次回は、Status Checksを判定するcheckタスクの改修を行ってみるのと、今回のinitタスクのafter処理として、新たに作成された保守用ユーザのSSH設定をCapistranoの設定ファイルに書き出す処理、そしてようやくパッケージアップデートを行う本来のデプロイ処理の導入までを作ってみようかと。
参考サイト
-
Name
タグはAWSマネージメントコンソールのインスタンス一覧の名前としても使われるので、このタグを付けておくと、作成したインスタンスが判別し易くなるためオススメします。 ↩