社内ではAWSが普通に使われているため、常々基礎からきちんと学びたいと考えていました。
そんな中、書籍「Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版」の社内勉強会が開催されることになりました。
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