Redhat Linux で ENI を使う
Tweetはじめに
あるシステムを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 などの通信を行うことができるようになります。