AWS MGNで移行したAmazon EC2インスタンスをTerraformで管理する

AWS

2022.12.5

Topics

ご覧頂きありがとうございます。今回はTerraformで構築したAWS環境にAWS MGN(Application Migration Service)で移行したEC2インスタンスをTerraform管理する検証です。
オンプレミス環境のサーバを簡単に移行できるAWS MGNから作成されたEC2インスタンスは、Terraform管理外の状態なので、Terraformへ反映させないといけません。
え?importして終わりでしょ??と思うかもしれませんが、実は落とし穴がありましたので、ぜひ最後までご覧いただけると嬉しいです。

想定読者

  • Terraformを使ってAWS環境を構築した経験がある
  • 手動で作成したEC2をコード管理したい

実現したいこと

  • Terraformを使ってVPCなどAWSネットワーク環境を構築しておく
  • AWS MGNで移行したEC2をコード管理する

なぜコード管理したいのか

一度コードを作ってしまえば、2台目以降や他の場面でも使い回せて効率的です。加えてAmazon CloudWatchを活用した複数監視設定及びアラーム設定、タグ管理なども一括で適用できる点が優れています。また、チームで管理・運用するならコードの方が追加や変更の差分が一目瞭然で管理しやすいです。

なぜAWS MGN?

工数をかけずクラウド化したい!というニーズは割と多いと感じており、AWS MGN最大の特徴であるサーバをまるっとそのままAWS環境へ移行できるシンプルな移行戦略が選択肢に入ります。今回は予めTerraformでAWS環境を用意した後、AWS MGNでオンプレミスサーバをEC2インスタンスに移行させ、移行後にTerraform管理に追加するシナリオを想定しました。

また、「そのまま」移行を目指す場合、IPアドレスについても事前に決定しておいたり、プライベートIPアドレスは変更しないことが理想!といったケースもあります。そこで、「ElasticIP」と「プライベートIPアドレスを指定したENI(ネットワークインターフェイス)」も予め用意しておき、AWS MGNによる移行後に手動でアタッチするものとします。

*AWS MGNの詳細や移行については省略します。弊社のオンデマンドセミナーで詳しい紹介がありますので、併せてご覧頂けると幸いです。オンプレミスからクラウド移行!AWS MGNによるリフト&シフトでシンプルに始めよう

AWS環境の用意

AWS環境構成図

上記のように、AWS環境に移行する為のネットワーク環境をTerraformにて作成しておきます。ENIのプライベートIPは、必然的にVPCやサブネットのIPアドレス範囲内となるので気を付けます。

AWS MGNを使って移行

MGNにて移行

左図のオンプレミスサーバからAWS MGNを使いAWS環境へ移行した図となります。用意しておいたENIをEC2にアタッチしています。この状態ではまだ、Terraformのstate上にはEC2は存在していない状態です。

EC2をimportする

applyの場合

通常のapplyの流れ

EC2を定義したコードを基に作成され、発行されたインスタンスIDがstateに保存されます。

importの場合

importの流れ

applyと同様にEC2用のコードを用意しておき、実際に存在しているインスタンスIDを指定してimportすることで、stateのEC2情報部分にインスタンスIDが保存されます。
importの時点では定義したコードと実際のEC2の設定に差分があっても問題ありませんが、EC2作成後に変更できないような差分は、次回のapplyで再作成になるので注意が必要です。

実際に実行するコマンドから見ていきます。

$ terraform import <リソース名.名前> <インスタンスID>

<リソース名.名前> … 自身が定義したコードのどのリソースかを指定します
<インスタンスID> … 実在しているインスタンスID

今回利用したコードはこちらです。

resource "aws_instance" "example01" {
    ami = "ami-xxxxxxxxxxx"
    instance_type = "t2.micro"
    key_name = "xxxxxxxxxxx"
    tenancy = "default"
    ebs_optimized = false
    
    tags = {
        "Name" = "example01"
    }

  network_interface {
    network_interface_id = "${aws_network_interface.eni_example01.id}"
    device_index         = 0
  }
    
    root_block_device {
        volume_size = 10
        volume_type = "gp2"
        delete_on_termination = true
        tags = {
            "Name" = "ebs_example01"
        }
    }
}

上記がEC2インスタンスを定義したコードです。1行目に注目してください。
resource “aws_instance” “example01”リソース名.名前です。今回importする対象が1台でコードもベタ書きですが、for_eachのループ分を使った複数EC2インスタンスを定義する場合は、配列番号を追加します。リソース名.名前.[配列番号]です。

実行してみる

$ terraform import aws_instance.example01 i-0f80141e319f9d39f
aws_instance.example01: Importing from ID "i-0f80141e319f9d39f"...
aws_instance.example01: Import prepared!
  Prepared aws_instance for import
aws_instance.example01: Refreshing state... [id=i-0f80141e319f9d39f]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

$ 

無事にimportできました!

差分を確認する ※超大事

planを実行し差分を見ていきます。

  # aws_instance.example01 must be replaced
-/+ resource "aws_instance" "example01" {
      ~ ami = "ami-0d11e717ea74ed486" -> "ami-072bfb8ae2c884cc4" # forces replacement

      - launch_template { # forces replacement
          - id      = "lt-0c595a1451e877f3f" -> null
          - name    = "mgn-test" -> null
          - version = "3" -> null

      + network_interface { # forces replacement
          + delete_on_termination = false
          + device_index          = 0
          + network_card_index    = 0
          + network_interface_id  = "eni-0944276d4c1f8202c"
        }


Plan: 1 to add, 0 to change, 1 to destroy.

そんなに甘くはなかった

結果を抜粋したものです。EC2再作成が発生するレベルの差分[forces replacement]が3点見つかりましたので、1つずつ見ていきます。

▼ 1点目

  # aws_instance.example01 must be replaced
-/+ resource "aws_instance" "example01" {
      ~ ami = "ami-0d11e717ea74ed486" -> "ami-072bfb8ae2c884cc4" # forces replacement

これは事前にTerraform上で指定したAMI IDと実際のAMI IDに差異がある為、TerraformのコードにあるAMIで作り直します!!て差分ですね。
AWS MGNを使ってオリジナルのAMIを作成しているので、実際のAMI IDに変更すれば解消されます。

▼ 2点目

      - launch_template { # forces replacement
          - id      = "lt-0c595a1451e877f3f" -> null
          - name    = "mgn-test" -> null
          - version = "3" -> null

この差分は起動テンプレート使わないで再作成する。という結果です。定義したコードには起動テンプレートの記載がありませんでした。一方AWS MGNでは起動テンプレートを利用してEC2を作成する為、差分になってしまいました。 起動テンプレートを利用した定義を追加させ、出力されているidversion を指定すれば解決です。

起動テンプレート追加したコード例

resource "aws_instance" "example01" {
    ami = "ami-0d11e717ea74ed486"
    instance_type = "t2.micro"
    key_name = "xxxxxxxxxxx"    
    tenancy = "default"
    ebs_optimized = false
    
    tags = {
        "Name" = "example01"
    }

   launch_template {
     id      = "lt-0c595a1451e877f3f"
     version = "3"
   }

  network_interface {
    network_interface_id = "${aws_network_interface.eni_example01.id}"
    device_index         = 0
  }
    
    root_block_device {
        volume_size = 10
        volume_type = "gp2"
        delete_on_termination = true
        tags = {
            "Name" = "ebs_example01"
        }
    }
}

▼ 3点目

      + network_interface { # forces replacement
          + delete_on_termination = false
          + device_index          = 0
          + network_card_index    = 0
          + network_interface_id  = "eni-0944276d4c1f8202c"
        }

ラスボスです。定義したコードにはEC2+ENIとなるようにしており、実際のEC2もENIをアタッチして作成しているにも関わらず差分が出てしまいました。その差分は + となっています。整理すると

  • 実際 → EC2 + ENI
  • state → EC2
  • コード → EC2 + ENI

となっており、 state 上ではENIが紐付いてない為 コード に従って追加するね!でもデバイス0は変更できないから作り直します!という結果です。
どうやらEC2をimportしてもENIの関連付けがされないようです…

$ terraform state show aws_instance.example01 |grep -w network_interface
$

terraform state showコマンドで確認してみましたが、やはりnetwork_interface という情報がない… 色々試したり先人がいないか探してみたらGithubのissueに上がっていました。aws_instance import fails to populate network_interface block #16567 22年12月時点ではまだ解消されていない様子。
そこで最終手段としてstateを編集して直接追加させます。

stateファイル編集

私の環境ではstateをS3に上げていますので、ローカルに落とす作業が必要です。最初からローカルにある場合は下記は不要です。

$ terraform state pull > terraform.tfstate

それではstateを編集します。修正するところは2ヶ所。
terraform.tfstate開いて4行目くらいにあるserial値を+1にする。

{
  "version": 4,
  "terraform_version": "1.2.5",
  "serial": 2,    ★ 元が1だったので+1にしている

network_interface で検索する。

            "network_interface": [],

上記を削除して、下記を追加する。

            "network_interface": [
              {
                "delete_on_termination": false,
                "device_index": 0,
                "network_card_index": 0,
                "network_interface_id": "eni-0944276d4c1f8202c"
              }
            ],

network_interface_idは対象となるものです。

stateを保存する。
ローカル以外にstateを管理している方は下記を実施。

$ terraform state push terraform.tfstate

差分を再確認

Plan: 0 to add, 1 to change, 0 to destroy.

replacementが無くなり、0 add , 0 destroy となりました!!ちなみに 1 change はTerraform上で定義したタグの追加が差分となります。後は差分をなくす為のapplyを実行して完了です。
以上でAWS MGNでオンプレミスサーバをAWS環境に移行して、コード管理するまでとなります。

まとめ

ここまでご覧いただき、ありがとうございます!importする際のENIの関連付けにひと手間かかりましたが、大きな問題もなくコード化できました。ENIを利用しなければさらにシンプルにできそうですね。
AWS MGNの詳細は省きましたが、手早く移行させ、さらにTerraformによるコード管理、運用もできることを検証できたのでよりAWS MGNが使いやすくなったなと感じました。
EC2をTerraformにimportする場面があった時に、この記事が少しでも役に立つと幸いです。

テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!

がっしー

いらっしゃいませ!2015年中途入社の"がっしー"と申します。 ド素人同然で入社し、毎日が四苦八苦。 AWSサービスを始めて触った時は、ポチポチするだけで簡単にサーバが立って素直にすごーって感動したのは今でも覚えてます。今はIaCも勉強中。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら