社内ではAWSが普通に使われているため、常々基礎からきちんと学びたいと考えていました。
そんな中、書籍「Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版」の社内勉強会が開催されることになりました。
- 作者: 玉川憲,片山暁雄,今井雄太,大澤文孝
- 出版社/メーカー: 日経BP
- 発売日: 2017/04/13
- メディア: 単行本
- この商品を含むブログを見る
これはちょうどよい機会だと思い、書籍をひと通り読んでみました。
書籍では、オンプレでやってたことをAWSで実現するにはどうすればよいかなど、AWSのインフラまわりの基礎的なところが書かれており、とてもためになりました。
ただ、手動でAWS環境を構築したため、このまま何もしないと忘れそうでした。
何か良い方法がないかを探したところ、AWS SDK for Python(Boto3)がありました。これを使ってPythonスクリプトとして残しておけば忘れないだろうと考えました。
なお、Boto3ではなくAnsibleでも構築できそうでしたが、今回はBoto3の使い方に慣れようと思い、
として、写経してみることにしました。
目次
- 環境
- IAMユーザーの作成と設定
- Boto3の準備
- Ansibleまわり
- 写経
- 写経時に作成したものを削除する
- その他参考
- ソースコード
環境
現在では、Boto3・AnsibleともにPython3で動くようです。
IAMユーザーの作成と設定
書籍では、AWSアカウントについては特に触れられていませんでした。
そこで、今年の3/11に開かれたJAWS DAYS 2017のセッション「不安で夜眠れないAWSアカウント管理者に送る処方箋という名のハンズオン」に従い、IAMユーザーを作成・使用することにしました。
- 不安で夜眠れないAWSアカウント管理者に送る処方箋という名のハンズオン | セッション | JAWS DAYS 2017
- [JAWS DAYS 2017 ワークショップ] 不安で夜眠れないAWSアカウント管理者に送る処方箋という名のハンズオン
主な設定内容は
- AWSアカウント・IAMユーザーともMFAを設定
- IAMユーザーのポリシーは
AdministratorAccess- 本来はもう少し弱い権限でも良い気がするが、今回はそこまで踏み込まず
- 請求アラートを有効化
- 0ドル以上のアラートと、1ドル以上のアラートの2つを用意
- Elastic IPをリリース忘れでの課金に気づけた
- AWSで請求アラートを設定する - Qiita
- 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を使うところでリージョンを指定しています。
あとは、
# ~/.aws/credentialsのmy-profileにあるキーを使う session = boto3.Session(profile_name='my-profile') # Clientを使う場合 client = session.client('ec2', region_name='ap-northeast-1') # Resourceを使う場合 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
VPCの作成
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'
},
]
)
VPCの一覧を確認
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領域',
]
}
]
)
アベイラビリティゾーンの確認
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,
}]
)
インターネットゲートウェイを作成
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',
}]
)
インターネットゲートウェイをVPCに紐づける
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)
ルートテーブルに名前をつける
- https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#routetable
- https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.RouteTable.create_tags
route_table = ec2_resource.RouteTable(route_table_id)
tag = route_table.create_tags(
Tags=[{
'Key': 'Name',
'Value': 'パブリックルートテーブル2',
}]
)
ルートテーブルをサブネットに割り当てる
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'])
ローカルに保存したキーのパーミッションを変更する
# modeは8進数表記がわかりやすい:Python3からはprefixが`0o`となった 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のポートを開ける
- https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#securitygroup
- 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='tcp',
# ポートは22だけ許可したいので、From/Toともに22のみとする
FromPort=22,
ToPort=22,
)
EC2インスタンスを立てる
- https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#service-resource
- https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.ServiceResource.create_instances
response = ec2_resource.create_instances(
ImageId=IMAGE_ID,
# 無料枠はt2.micro
InstanceType='t2.micro',
# 事前に作ったキー名を指定
KeyName=key_pair_name,
# インスタンス数は、最大・最小とも1にする
MaxCount=1,
MinCount=1,
# モニタリングはデフォルト = Cloud Watchは使わないはず
# Monitoring={'Enabled': False},
# サブネットにavailability zone が結びついてるので、明示的なセットはいらないかも
# Placement={'AvailabilityZone': availability_zone},
# セキュリティグループIDやサブネットIDはNetworkInterfacesでセット(詳細は以下)
# SecurityGroupIds=[security_group_id],
# SubnetId=subnet_id,
NetworkInterfaces=[{
# 自動割り当てパブリックIP
'AssociatePublicIpAddress': is_associate_public_ip,
# デバイスインタフェースは1つだけなので、最初のものを使う
'DeviceIndex': 0,
# セキュリティグループIDは、NetworkInterfacesの方で割り当てる
# インスタンスの方で割り当てると以下のエラー:
# Network interfaces and an instance-level security groups may not be specified on the same request
'Groups': [security_group_id],
# プライベートIPアドレス
'PrivateIpAddress': private_ip,
# サブネットIDも、NetworkInterfacesの方で割り当てる
# インスタンスの方で割り当てると以下のエラー:
# Network interfaces and an instance-level subnet ID may not be specified on the same request
'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にはhostsファイルの[web]かホスト名(webserver)を指定する - 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のポートを開ける
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にはhostsファイルの[web]かホスト名(webserver)を指定する - 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
# DomainのvpcはVPC、standardはEC2-Classic向け 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)。
流れとしては、作成したのとは逆順で削除していく形となります。
その他参考
- AWSの保守運用を自動化するためのアーキテクチャ - Qiita
- Automating AWS With Python and Boto3
- Ansibleをはじめる人に。 - Qiita
- Ansibleで始めるインフラ構築自動化
- Ansible実践入門 | Developers.IO
- AnsibleによるEC2インスタンスの構築 | Developers.IO
ソースコード
GitHubに上げました。
thinkAmi-sandbox/syakyo-aws-network-server-revised-edition-book