By

Redhat Linux で ENI を使う

はじめに

あるシステムをAWSで構築していたら、ちょっと特殊な事情でENI(Elastic Network Interface)を使う必要が出てきました。

Amazon Linux では、ENIはコマンド一発で使用できるので難しいことはないのですが、そのシステムで使用するAMIが、RHEL(Red Hat Enterprise Linux)だったために、手動の設定が必要でした。

かなり特殊な組み合せになるためか、ネットで検索しても参考になる情報が少なくて苦労したので、まとめておきます。

背景

ここで説明しようとする方法は、RHEL + ENI + ASG(Auto Scaling Group)という組み合わせのみで必要になります。最初にこれが必要になった背景を説明します。

デジカでは、EC2インスタンスは CloudFormation で ASG を作成して、そこから起動するようにしています。一台で運用する小規模なシステムでも、同じ運用になります。この方法には以下のようなメリットがあります。

  • 自然な形で障害時の自動復旧の仕組みができる
  • インフラ回りの設定が、CloudFormation テンプレートに集約され、github リポジトリ上での管理ができる
  • 永続化すべきデータを明確化できる

また、インフラの初期設定時にも、UserDataとして設定するスクリプト等のデバッグが簡単にできます。というのは、単にインスタンスを終了すれば、ASGによって同じ設定のインスタンスが数分で起動するので、条件を変えたりして再実行することが容易になるからです。

デメリットとしては、全ての設定をUserData(Cloud Init)で、人の介入なしに行なう必要があるので、Web Console 等で手動設定するより難しいということがあります。

このエントリのテーマである、RHEL + ENI というレアな組み合せがあったりすると、設定できるまで、時間がかかってしまうことになります。

ENIが必要になったのは、構築しようとしたシステムが、固定のIPアドレスで運用することを前提とするパッケージを使用していた為です。ASGを使わずにEC2インスタンスを直接起動するのであれば、awscli でも CloudFormation でも、IPアドレスを指定して起動することができますが、ASGで起動する場合には、動的アドレス以外の選択肢がありません。

そこで、ENIに固定のアドレスを割り当てておいて、インスタンスが起動されてから、それをアタッチするようにしました。

Amazon Linux の場合

ENIは、Amazon Linux であれば、簡単に使用できます。まず、最初に、次のコマンドでENIを作成します。

$ aws ec2 create-network-interface --private-ip-address=10.x.y.99 --subnet-id=subnet-xxxxxxxxx

これを、Amazon Linux の EC2インスタンスに割り当てるには、次のコマンドを作成します。

$ aws --region=ap-northeast-1 ec2 attach-network-interface --device-index=1 --instance-id=i-xxxxxxxxx --network-interface-id=eni-xxxxxxxx

Amazon Linuxでは、このコマンドだけで、すぐにこのENIを使用できます。

ENIのアタッチは、ハードウェアのエミュレーションのレベルで、ネットワークアダプタを接続しているだけです。ネットワークが使える状態になっているということは、OSの設定が自動的に行なわれているようです。この仕組みについて簡単に調べてみました。

これは、Amazon Linux に最初からインストールされているaws/ec2-net-utilsというパッケージによって行なわれています。起点となっているのは、ec2-net-utils/ec2net.hotplugというスクリプトで、ENIの接続時にこれが起動され、/etc/sysconfig/network-scripts/ifcfg-eth1 というファイルが次のような内容で作成されます。

DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes
TYPE=Ethernet
USERCTL=yes
PEERDNS=yes
DHCPV6C=yes
DHCPV6C_OPTIONS=-nw
PERSISTENT_DHCLIENT=yes
RES_OPTIONS="timeout:2 attempts:5"
DHCP_ARP_CHECK=no

この設定を使って、OS側の設定が行なわれているようです。これらについては、下記のページに詳しく説明されています。

Redhat Linux の場合

ところが、RHEL には、ec2-net-utils に相当するパッケージが存在しません。従って、aws attach-network-interface の後に、OS側の設定を自分で行なう必要があります。

いろいろ試行錯誤しながら調べたところ、REHLでは、Network Manager によって、ネットワークの管理が行なわれているので、nmcli というコマンドを使って設定するようです。

次のコマンドによって、ENIのIPアドレスを使って、接続できるようになります。

$ nmcli con add type ethernet ifname eth1 con-name eth1
$ nmcli con down 'System eth0'

nmcli con add ... は、新しいデバイスのネットワーク接続を作成するコマンドです。ENIはDHCPをサポートしており、DHCPによる自動設定が可能なので、type 以外のオプションはデフォルトで使用できます。

このコマンドを入力すると、/etc/sysconfig/network-scripts/ifcfg-eth1 が作成されます。

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=eth1
UUID=xxxxxxxx
DEVICE=eth1
ONBOOT=yes

これで、eth1 のアドレスやルーティング定義は作られるのですが、私の環境では、これに加え、eth0 を切り離す必要がありました。これが nmcli con down のコマンドになります。

Amazon Linux では、eth0を有効化したままでeth1 が使えたました。RHEL では上記の設定で、ルーティング等のOSの設定は同じ状態になったように見えるのですが、なぜか、eth1 で新しく設定したIPアドレスの通信は行なえませんでした。eth0 を無効にした後は、問題なく使えました。

CloudFormation テンプレートサンプル

以上の内容を、CloudFormation のテンプレートとして作成してみました。

AWSTemplateFormatVersion: 2010-09-09
Description: Eni test with Redhat Linux

Resources:
  MyNetworkInterface:
    Type: "AWS::EC2::NetworkInterface"
    Properties: 
      GroupSet:
        - sg-56476e2e
      PrivateIpAddress: "10.x.y.99"
      SubnetId: subnet-xxxxxxxxx
      Tags:
        - Key: Name
          Value: eni-test

  MyEC2:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.small
      ImageId: ami-6b0d5f0d
      SecurityGroupIds:
        - sg-xxxxxxx
      SubnetId: subnet-xxxxxxx
      KeyName: mykey
      IamInstanceProfile: myprofile-name
      Tags:
        - Key: Name
          Value: eni-test
      UserData: # 
        Fn::Base64:
          Fn::Sub:
            - |
              #cloud-config
              ---
              runcmd:
                - set -ex
                - # awscli のインストール
                - curl "https://bootstrap.pypa.io/get-pip.py" -o "/tmp/get-pip.py"
                - python /tmp/get-pip.py
                - pip install awscli
                ー # 自分のインスタンスIDを取得する
                - export INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) && echo $INSTANCE_ID
                - # ENI のアタッチ
                - aws --region ap-northeast-1 ec2 attach-network-interface --instance-id $INSTANCE_ID --device-index 1 --network-interface-id ${NetworkInterface} 
                - # OS側の設定
                - nmcli con add type ethernet ifname eth1 con-name eth1
                - nmcli con down 'System eth0'
            - { NetworkInterface: !Ref MyNetworkInterface }

これを使うには、あらかじめ、以下のリソースを別途作成しておく必要があります。

  • SecurityGroup
  • Subnet
  • InstanceProfile

このうち、InstanceProfile は、ec2 attach-network-interface の権限があるものにしてください。

通常は、同じテンプレートの中に、これらの設定も含めることになると思います。また、ASGを使う場合も、UserData の部分は同じように使うことができます。

このテンプレートをdeploy すると、そのまま固定のIPアドレス(この例では、10.x.y.99)によって ssh などの通信を行うことができるようになります。

一緒にユニークな決済サービスを作ってくれる Rails エンジニアを募集中です!
多国籍なメンバーと一緒に仕事をしてみませんか?詳細はこちらのリンクです:D