社内ではAWSが普通に使われているため、常々基礎からきちんと学びたいと考えていました。
そんな中、書籍「Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版」の社内勉強会が開催されることになりました。
これはちょうどよい機会だと思い、書籍をひと通り読んでみました。
書籍では、オンプレでやってたことをAWSで実現するにはどうすればよいかなど、AWSのインフラまわりの基礎的なところが書かれており、とてもためになりました。
ただ、手動でAWS環境を構築したため、このまま何もしないと忘れそうでした。
何か良い方法がないかを探したところ、AWS SDK for Python(Boto3)
がありました。これを使ってPythonスクリプトとして残しておけば忘れないだろうと考えました。
なお、Boto3ではなくAnsibleでも構築できそうでしたが、今回はBoto3の使い方に慣れようと思い、
として、写経してみることにしました。
目次
環境
現在では、Boto3・AnsibleともにPython3で動くようです。
IAMユーザーの作成と設定
書籍では、AWSアカウントについては特に触れられていませんでした。
そこで、今年の3/11に開かれたJAWS DAYS 2017のセッション「不安で夜眠れないAWSアカウント管理者に送る処方箋という名のハンズオン」に従い、IAMユーザーを作成・使用することにしました。
主な設定内容は
- AWSアカウント・IAMユーザーともMFAを設定
- IAMユーザーのポリシーは
AdministratorAccess
- 本来はもう少し弱い権限でも良い気がするが、今回はそこまで踏み込まず
- 請求アラートを有効化
- Security Token Service リージョンの設定
- 以下だけ有効化
- 米国東部(バージニア北部)
- アジアパシフィック(東京)
としました。
Boto3の準備
今回はawscli
を使わず、Boto3だけで環境を準備します。
READMEのQuick Startに従って、環境ファイルを用意します。
boto/boto3: AWS SDK for Python
~/.aws/credentials
こちらに aws_access_key_id
とaws_secret_access_key
を設定します。
【鍵管理】~/.aws/credentials を唯一のAPIキー管理場所とすべし【大指針】 | Developers.IO
今回は試しにデフォルト以外のprofile(my-profile)を使うことにしてみました。
boto3 で デフォルトprofile以外を使う - Qiita
[my-profile]
aws_access_key_id = YOUR_KEY
aws_secret_access_key = YOUR_SECRET
~/.aws/config
こちらにはデフォルトリージョンを設定します。
READMEにはus-east-1
と記載されています。
東京リージョンの場合は何を指定すればよいかを探したところ、 ap-northeast-1
を使うのが良さそうでした。
AWS のリージョンとエンドポイント - アマゾン ウェブ サービス
[default]
region=ap-northeast-1
ただ、自分の書き方が悪いせいか、上記のように書いてもデフォルトリージョンとして設定されなかったため、Boto3を使うところでリージョンを指定しています。
あとは、
session = boto3.Session(profile_name='my-profile')
client = session.client('ec2', region_name='ap-northeast-1')
resource = session.resource('ec2', region_name='ap-northeast-1')
のようにして、アクセスキーなどを持ったclientとresourceを取得し、それを使って操作します。
なお、clientやresourceの第一引数にはAWS サービスの名前空間を使えば良さそうでした。
AWS サービスの名前空間 | Amazon リソースネーム (ARN) と AWS サービスの名前空間 - アマゾン ウェブ サービス
今回使用するAmazon EC2とAmazon VPCの名前空間は、ともに ec2
のようでした。
Ansibleまわり
途中でEC2の設定をするため、Ansibleまわりの準備もしておきます。
ansible.cnfでssh_configを設定する | Developers.IO
ssh_config
HostNameのxxx.xxx.xxx.xxx
は、EC2を立てた時にパブリックIPアドレスへと差し替えます。
Host webserver
User ec2-user
HostName xxx.xxx.xxx.xxx
# IdentityFileはカレントディレクトリに置いておけば良い
IdentityFile syakyo_aws_network_server2.pem
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
ansible.cnf
Fオプションのファイル(ssh_config
)は、プルパスでなくても問題ありませんでした。
[ssh_connection]
ssh_args = -F ssh_config
Inventoryファイル(hosts)
ここに記述するホスト名は、ssh_configのHostの値と一致させます。今回はwebserver
となります。
[web]
webserver
あとは、ターミナルから
$ ansible-playbook -i hosts ch4_apache.yml
のようにして、EC2インスタンスに対して実行します。
写経
今回写経したコードを載せておきます。
なお、だいたいのものはClientとResourceServiceのどちらでも操作できました。
ただ、戻り値が
- Clientは、dict
- ResourceServiceは、各オブジェクト
だったので、ResourceServiceの方が扱いやすいのかなと感じました。
Chapter2
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_vpc
response = ec2_client.create_vpc(
CidrBlock='192.168.0.0/16',
AmazonProvidedIpv6CidrBlock=False,
)
vpc_id = response['Vpc']['VpcId']
VPCに名前をつける
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Vpc.create_tags
vpc = ec2_resource.Vpc(vpc_id)
tag = vpc.create_tags(
Tags=[
{
'Key': 'Name',
'Value': 'VPC領域2'
},
]
)
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_vpcs
response = ec2_client.describe_vpcs(
Filters=[
{
'Name': 'tag:Name',
'Values': [
'VPC領域',
]
}
]
)
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_availability_zones
response = ec2_client.describe_availability_zones(
Filters=[{
'Name': 'state',
'Values': ['available'],
}]
)
VPCにサブネットを作成
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Vpc.create_subnet
vpc = ec2_resource.Vpc(vpc_id)
response = vpc.create_subnet(
AvailabilityZone=availability_zone,
CidrBlock=cidr_block,
)
サブネットに名前をつける
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Subnet.create_tags
tag = ec2_subnet.create_tags(
Tags=[{
'Key': 'Name',
'Value': subnet_name,
}]
)
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_internet_gateway
response = ec2_client.create_internet_gateway()
インターネットゲートウェイに名前をつける
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.InternetGateway.create_tags
internet_gateway = ec2_resource.InternetGateway(internet_gateway_id)
tags = internet_gateway.create_tags(
Tags=[{
'Key': 'Name',
'Value': 'インターネットゲートウェイ2',
}]
)
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.InternetGateway.attach_to_vpc
internet_gateway = ec2_resource.InternetGateway(internet_gateway_id)
response = internet_gateway.attach_to_vpc(
VpcId=vpc_id,
)
ルートテーブルを作成する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_route_table
response = ec2_client.create_route_table(VpcId=vpc_id)
ルートテーブルに名前をつける
route_table = ec2_resource.RouteTable(route_table_id)
tag = route_table.create_tags(
Tags=[{
'Key': 'Name',
'Value': 'パブリックルートテーブル2',
}]
)
ルートテーブルをサブネットに割り当てる
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.RouteTable.associate_with_subnet
route_table = ec2_resource.RouteTable(route_table_id)
route_table_association = route_table.associate_with_subnet(SubnetId=subnet_id)
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.RouteTable.create_route
route_table = ec2_resource.RouteTable(route_table_id)
route = route_table.create_route(
DestinationCidrBlock='0.0.0.0/0',
GatewayId=internet_gateway_id,
)
ルートテーブルを確認する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_route_tables
response = ec2_client.describe_route_tables()
Chapter3
キーペアを作成してローカルに保存する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_key_pair
# キーペアを作成していない場合はキーペアを作成する
if not os.path.exists(KEY_PAIR_FILE):
response = ec2_client.create_key_pair(KeyName=KEY_PAIR_NAME)
print(inspect.getframeinfo(inspect.currentframe())[2], response['KeyName'])
with open(KEY_PAIR_FILE, mode='w') as f:
f.write(response['KeyMaterial'])
ローカルに保存したキーのパーミッションを変更する
os.chmod(KEY_PAIR_FILE, mode=0o400)
セキュリティグループを作成する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_security_group
response = ec2_client.create_security_group(
Description=name,
GroupName=name,
VpcId=vpc_id,
)
セキュリティグループでSSHのポートを開ける
security_group = ec2_resource.SecurityGroup(security_group_id)
response = security_group.authorize_ingress(
CidrIp='0.0.0.0/0',
IpProtocol='tcp',
# ポートは22だけ許可したいので、From/Toともに22のみとする
FromPort=22,
ToPort=22,
)
response = ec2_resource.create_instances(
ImageId=IMAGE_ID,
InstanceType='t2.micro',
KeyName=key_pair_name,
MaxCount=1,
MinCount=1,
NetworkInterfaces=[{
'AssociatePublicIpAddress': is_associate_public_ip,
'DeviceIndex': 0,
'Groups': [security_group_id],
'PrivateIpAddress': private_ip,
'SubnetId': subnet_id,
}],
TagSpecifications=[{
'ResourceType': 'instance',
'Tags': [{
'Key': 'Name',
'Value': instance_name,
}]
}],
)
EC2インスタンスがrunningになるまで待つ
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Instance.wait_until_running
ec2_instance.wait_until_running()
Chapter4
GUIの「DNSホスト名の編集」を実行する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.modify_vpc_attribute
response = ec2_client.modify_vpc_attribute(
EnableDnsHostnames={'Value': True},
VpcId=vpc_id,
)
AnsibleでApacheをインストールし、起動設定にする
- hosts: webserver
become: yes
tasks:
- name: install Apache
yum: name=httpd
- name: Apache running and enabled
service: name=httpd state=started enabled=yes
Chapter6
Chapter6ではプライベートサブネットにEC2インスタンスを立てます。
その方法はパブリックサブネットと同様なため、ここでは差分のみ記載します。
パブリックサブネットのAvailability Zoneを取得する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#subnet
ec2_subnet = ec2_resource.Subnet(subnet_id)
return ec2_subnet.availability_zone
セキュリティグループでICMPのポートを開ける
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.SecurityGroup.authorize_ingress
security_group = ec2_resource.SecurityGroup(security_group_id)
response = security_group.authorize_ingress(
CidrIp='0.0.0.0/0',
IpProtocol='icmp',
# ICMPの場合、From/Toともに -1 を設定
FromPort=-1,
ToPort=-1,
)
Ansibleで、ローカルのSSH鍵をWebサーバへSCPを使って転送する
- hosts: webserver
tasks:
- name: copy private-key to webserver
copy: src=./syakyo_aws_network_server2.pem dest=~/ owner=ec2-user group=ec2-user mode=0400
Chapter7
Elastic IPを取得する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.allocate_address
response = ec2_client.allocate_address(Domain='vpc')
NATゲートウェイを作成し、Elastic IPを割り当て、パブリックサブネットに置く
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_nat_gateway
response = client.create_nat_gateway(
AllocationId=allocation_id,
SubnetId=subnet_id,
)
NATゲートウェイがavailableになるまで待つ
NATゲートウェイを作成した直後はまだavailableになっていないため、availableになるまで待ちます。
手元では約2分ほどかかりました。
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Waiter.NatGatewayAvailable
waiter = ec2_client.get_waiter('nat_gateway_available')
response = waiter.wait(
Filters=[{
'Name': 'state',
'Values': ['available']
}],
NatGatewayIds=[nat_gateway_id]
)
メインのルートテーブルのIDを取得する
NATゲートウェイのエントリを追加するため、メインのルートテーブルのIDを取得します。
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_route_tables
response = ec2_client.describe_route_tables(
Filters=[
{
'Name': 'association.main',
'Values': ['true'],
},
{
'Name': 'vpc-id',
'Values': [vpc_id],
}
]
)
main_route_table_id = response['RouteTables'][0]['RouteTableId']
メインのルートテーブルにNATゲートウェイのエントリを追加する
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.RouteTable.create_route
route_table = ec2_resource.RouteTable(route_table_id)
route = route_table.create_route(
DestinationCidrBlock='0.0.0.0/0',
NatGatewayId=nat_gateway_id,
)
写経時に作成したものを削除する
ここまででGUIで行った内容をBoto3 & Ansibleで実装しました。
せっかくなので、作成したものをBoto3で削除するPythonスクリプトも作成してみました (boto3_ansible/clear_all.py)。
流れとしては、作成したのとは逆順で削除していく形となります。
その他参考
GitHubに上げました。
thinkAmi-sandbox/syakyo-aws-network-server-revised-edition-book