2019(2)の13日目です。2019年4月に新卒で入社し、7月からAWSを触り始めました。Image)管理について考えなければならない場面に直面しました。その際にEC2(Linux)のユーザ管理に悩まされたので思ったこと、最終的に行きついた管理方法を書いていきます。Builderが発表されたことによって、Packerを使用する機会が減っていくかもしれないので、供養の意味も込めてPackerについても軽く触れます。Packerとは何かPackerはHashiCorp社が出しており、AWSのみならず、GCP、Azure、Dockerといった様々な環境のイメージを作成できるツールです。buildコマンドを実行することで、インスタンスを起動しJSONに記述された手順通りに設定がおこなわれます。設定がすべて完了したらそのインスタンスをイメージ化し、使用したインスタンスはterminateされます。以下は、Packerでshellやansibleを使う際の記述例です。}]非常に簡単な例しか提示していませんが、他にもprovisionersは多く用意されており、Packerを使うことでコードベースで自由にAMIを構築・管理することが可能になります。Packerの利点・欠点ここではAMIを手動作成する場合と比較します。手動でAMIを作成する場合、AWSマネジメントコンソール上でインスタンスを右クリックし、イメージの作成を選択することでインスタンスをイメージ化することが可能です。コードからAMIを簡単に再現できるためEC2のスナップショットを取る必要がない手動でAMIを作成する場合、改修を加えていく過程でどうやって作成されたかわからなくなりがちです。これを避けるためには、作成手順を逐一ドキュメント化する必要があり、非常に面倒です。定期的にEC2のスナップショットをとるといったような、障害時対応も考える必要があります。Packerはこれらの問題を解決してくれます。一方で、Packerを使ううえで面倒だなと感じたこともいくつかありました。Packer実行時にAWS上のインスタンスへのsshが可能である必要があることユーザ作成など軽微な変更でもPackerのコードを変更する必要があること1に関して、社内ネットワークから自由にsshできない場面もあるのではないでしょうか。Packerで一時的に立ち上がるインスタンスのグローバルIPは毎回変更されるため、特定IPのみssh許可するといった対策もとれません。私の場合は社用PCのローカルからPackerを実行することを諦めました。余談ですが、デフォルトVPCを削除した状態だとPackerが実行できなくなるらしいです。commit、push、mergeといった一連の流れをおこなう必要があり、頻度が高くなるとストレスを感じます。構築したもの前節で述べたようにPackerの実行環境と、AMIから作成するEC2のユーザ管理に悩まされた結果、これらを解決するために以下のものを構築しました。今回はLinuxユーザとIAMユーザと同期させるスクリプトについて述べます。今回、Linuxユーザを作成する際のマスタとしてIAMユーザを採択しました。理由としては、別途Linuxユーザの一覧といったものを管理するのが面倒だったのが正直なところです。DB認証を利用することで、各DBごとにユーザを管理するのではなく、IAMユーザ情報をもとにRDSに接続することが可能です。これと同じことをEC2でも実現したいというモチベーションです。絶対にすでに誰かがやっていると思ったのでまずは先駆者を探しました。参考記事IAMで踏み台ホストのユーザーとSSH公開鍵を一元管理するこちらの記事では、/home直下のユーザディレクトリ名とaws-cliで取得したIAMユーザ一覧を比較しユーザの追加・削除を行っています。基本部分はこの記事を参考にしているため、今回は追加点・変更点などについて記述します。上記で紹介した記事のままでも問題なく動作したのですが、私たちの環境では以下の要望が生まれました。keyの生成・ユーザへの配布までやってほしい1.特定のIAMグループに属するユーザのみを対象としたいすべてのIAMユーザをLinuxユーザとする場合は問題ないのですが、AWSコンソールしか触らないユーザやAWSのシステムユーザが全員Linuxユーザになるのは避けたいためです。こちらもaws-cliで実現できます。先ほど紹介したユーザの一覧取得と合わせると以下のように既述することが出来ます。※今回は実行時間を気にせず作成してしています。速さを求める方は適宜変更してください。参考記事ではIAMユーザーのCodeCommit用SSH公開鍵を取得するという方法でKeyを管理しています。実際には以下のコマンドで取得する流れになるのですが、複数のインスタンスが存在する場合、すべてのインスタンスにおいて同じKeyを使いまわす状況になってしまいます。authorized_keysdoneインスタンスごとにIAMユーザを作ることでも対応可能ですが、今回はIAMユーザは1人1つで実現したかったためS3に公開鍵を配置するよう変更しました。lsコマンドを用いて、その結果が空か否かでkeyの存在を判定しています。{{}}で囲まれている変数に関してはPacker実行時に定義された環境変数を埋め込むようにしています。keyの生成・ユーザへの配布までやってほしいこれに関しては賛否両論あると思います。秘密鍵の管理をどうするかはユースケースによります。今回はEC2へのアクセスにMFAを強制しており、秘密鍵を置くS3へのアクセス制限も厳しいことからS3で管理を行っています。秘密鍵を絶対にローカル以外に置きたくないという場合はローカルで公開鍵、秘密鍵を作成し公開鍵をS3にアップロードするという形が良いと思います。Drive経由などで渡すくらいであれば、IAM・IPによってアクセス制限されたS3に置くのも変わらないのではないかという判断です。出来るだけユーザ発行、削除におけるオペレーションを減らしたかったので秘密鍵の配布までスクリプトで行っています。"s3://{{s3_bucket_name}}/{{env}}/secret/${iam_user}/{{env}}_${iam_user}.pem"公開鍵がS3に存在しない場合のみ上記の処理を実行し、鍵をS3に配置します。AutoHealingやAMIの更新によってインスタンスが再作成されたときは、S3にある公開鍵をインスタンスに登録するため、秘密鍵は共通のものを使い続けることが出来ます。次に、IAMユーザの権限設定について述べます。IAMポリシーでは${aws:username}のように変数が用意されており利用できます。これを利用することで、各ユーザ毎にポリシーを作成する必要はなく、以下のようなポリシーをIAMグループにアタッチしておくことで各ユーザは自分の名前の付いたObject配下のリソースのみをGetすることが可能になります。これを利用して、ユーザが自分の秘密鍵のみS3からGetできる仕組みを実現しています。Packerへの組み込みprovisionerとしてansibleを利用しています。templateモジュールを使うとファイルをコピーする際に、ファイル内の{{}}で囲われた変数が、Packerで定義されている環境変数に置き換わるので便利です。今はcronで毎時実行していますが、Linuxユーザの反映に最大1時間かかってしまうため、このあたりはユースケースに合わせて変更してください。Healingした場合やAMI更新時にLinuxユーザが即時反映されるように、reboot時にjobを実行する設定も必要です。まとめPackerを用いることでAMIをコードで管理できますが、コード上でLinuxユーザの管理を行うのは変更頻度、Key管理の観点からあまり良い方法ではないと感じました。そこで今回作成したスクリプトを使うと以下のことが可能になり、Packerと組み合わせることで運用が容易なAMIを作成することが出来ます。upLoginユーザーは見つかりませんでした。