记录一下在AWS上搭建云服务的过程。
假设我们现在要搭建一个云服务系统,这个系统要提供网站服务,用户数据存储在数据库。需要提供一个高可用性,高性能,可灵活扩展的一个方案。
方案设计如下:
1. 定义一个虚拟私有云VPC
2. 定义一个公有子网,用于放置跳板机。定义两个私有子网,放置WebServer。定义两个私有子网,放置Aurora Cluster
3. Web服务器,采用Auto Scaling Group来自动创建,制定minsize和desired capacity为2,表示最少2台web服务器运行,提供高可用性
4. 定义ELB, 指向Web服务器的Auto scaling group。ELB会检查HTTP Status来进行切换。
5. 配置Aurora Cluster,定义一个Primary Instanance和一个Read Replica。Web服务器的写操作都去到Primary Instance的Endpoint,读操作都去到Read Replica的Endpoint。如果需要进一步提高数据库的读性能,可以增加Read Replica,最多可以去到15个。(略)
6. 配置CloudFront作为内容分发,加速Web静态资源的访问速度。(略)
7. 采用CloudFormation模板来定义所需要的资源和配置,实现架构即代码(IaC)
以下是方案的架构图:
具体的实现如下:
1. 定义VPC, 绑定一个Internet Gateway,使得VPC中的公有子网可以通过IGW与互联网通讯。CloudFormation的JSON描述如下:
"VPC": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.0.0.0/16"
}
},
"InternetGateway": {
"Type": "AWS::EC2::InternetGateway",
"Properties": {
}
},
"VPCGatewayAttachment": {
"Type": "AWS::EC2::VPCGatewayAttachment",
"Properties": {
"VpcId": {"Ref": "VPC"},
"InternetGatewayId": {"Ref": "InternetGateway"}
}
}
2. 定义一个公有子网以及相应的路由表和路由,创建一个Bastion主机,放置在这个公有子网中,并定义Security Group,只允许入站SSH的22端口
"SubnetPublicSSHBastion": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": "ap-east-1a",
"CidrBlock": "10.0.1.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"RouteTablePublicSSHBastion": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"RouteTableAssociationPublicSSHBastion": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetPublicSSHBastion"},
"RouteTableId": {"Ref": "RouteTablePublicSSHBastion"}
}
},
"RoutePublicSSHBastionToInternet": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {"Ref": "RouteTablePublicSSHBastion"},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {"Ref": "InternetGateway"}
},
"DependsOn": "VPCGatewayAttachment"
},
"SSHSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Allow SSH",
"VpcId": {"Ref": "VPC"}
}
},
"AllowInboundSSH": {
"Type": "AWS::EC2::SecurityGroupIngress",
"Properties": {
"GroupId": {"Ref": "SSHSecurityGroup"},
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": "0.0.0.0/0"
}
},
"BastionEC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-c790d6b6",
"InstanceType": "t3.nano",
"KeyName": {"Ref": "KeyName"},
"NetworkInterfaces": [{
"AssociatePublicIpAddress": "true",
"DeleteOnTermination": "true",
"SubnetId": {"Ref": "SubnetPublicSSHBastion"},
"DeviceIndex": "0",
"GroupSet": [{"Ref": "SSHSecurityGroup"}]
}]
},
"DependsOn": "VPCGatewayAttachment"
}
3. 创建一个自定义的AMI映像,基于ubuntu 18,安装Flask和Gunicorn,用Flask来搭建一个简单的Web服务index.py,返回当前服务器的hostname,代码如下:
from flask import Flask
import socket
app = Flask(__name__)
@app.route('/')
def index():
return 'The server name is '+socket.gethostname()+'
'
if __name__ == '__main__':
from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)
app.run()
通过Gunicorn来部署Flask应用
gunicorn -w 1 -b 0.0.0.0:5000 -D index:app
可以参考以下博客在Ubuntu 18环境下设置开机自动运行脚本,来自动运行Gunicorn
https://blog.csdn.net/time_future/article/details/85805298
以上步骤完成后,就可以在AWS控制台中,在EC2 Instance上选择创建AMI,之后我们就可以用这个自定义的AMI来启动EC2 Instance
4. 定义一个Launch Configuration,指定Web Server需要用到的Instance类型,AMI ID,以及Security Group。
"HTTPSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Allow HTTP",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"SourceSecurityGroupId": {"Ref": "SSHSecurityGroup"}
},
{
"IpProtocol": "tcp",
"FromPort": "5000",
"ToPort": "5000",
"SourceSecurityGroupId": {"Ref": "ApplicationLoadBalancerSecurityGroup"}
}
],
"VpcId": {"Ref": "VPC"}
}
},
"LaunchConfiguration": {
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Properties": {
"InstanceMonitoring": false,
"SecurityGroups": [{"Ref": "HTTPSecurityGroup"}],
"ImageId": "ami-0f2685ba13c898c26",
"KeyName": {"Ref": "KeyName"},
"AssociatePublicIpAddress": false,
"InstanceType": {"Ref": "InstanceType"}
}
}
5. 定义一个Auto scaling group,自动启动所需的Web服务器。指定最小的服务器数目为2,这样可以保证容错性。DesiredCapacity可以根据服务器的负荷或者ELB的网络连接数等指标来动态设定,以满足业务增长的需求。关联两个可用区的各一个子网到这个Auto scaling group,AWS会自动平均分配服务器到这两个可用区中。
"SubnetPrivateWebserver1": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": "ap-east-1a",
"CidrBlock": "10.0.2.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"SubnetPrivateWebserver2": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": "ap-east-1b",
"CidrBlock": "10.0.3.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"AutoScalingGroup": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"TargetGroupARNs" : [{"Ref" : "ALBTargetGroup"}],
"LaunchConfigurationName": {"Ref": "LaunchConfiguration"},
"DesiredCapacity": 2,
"MinSize": 2,
"MaxSize": 2,
"VPCZoneIdentifier": [
{"Ref": "SubnetPrivateWebserver1"},
{"Ref": "SubnetPrivateWebserver2"}
]
},
"DependsOn": "VPCGatewayAttachment"
}
6. 定义负载均衡器ALB,这个ALB关联两个公共子网,定义这两个子网的路由,以及Security Group,ALB将Web访问请求转发到后端的Web Server的5000端口
"SubnetLB1": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": "ap-east-1a",
"CidrBlock": "10.0.4.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"SubnetLB2": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"AvailabilityZone": "ap-east-1b",
"CidrBlock": "10.0.5.0/24",
"VpcId": {"Ref": "VPC"}
}
},
"RouteTableLB": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {"Ref": "VPC"}
}
},
"RouteTableAssociationLBSubnet1": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetLB1"},
"RouteTableId": {"Ref": "RouteTableLB"}
}
},
"RouteTableAssociationLBSubnet2": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {"Ref": "SubnetLB2"},
"RouteTableId": {"Ref": "RouteTableLB"}
}
},
"RouteWebServerToInternet": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {"Ref": "RouteTableWebServer"},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {"Ref": "InternetGateway"}
},
"DependsOn": "VPCGatewayAttachment"
},
"ApplicationLoadBalancerSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "elb-sg",
"VpcId": {"Ref": "VPC"},
"SecurityGroupIngress": [{
"CidrIp": "0.0.0.0/0",
"FromPort": 80,
"IpProtocol": "tcp",
"ToPort": 80
}]
}
},
"ApplicationLoadBalancer": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Subnets": [
{"Ref": "SubnetLB1"},
{"Ref": "SubnetLB2"}
],
"SecurityGroups": [{"Ref": "ApplicationLoadBalancerSecurityGroup"}]
},
"DependsOn": "VPCGatewayAttachment"
},
"ALBListener": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"Properties": {
"DefaultActions": [{
"Type": "forward",
"TargetGroupArn": {"Ref": "ALBTargetGroup"}
}],
"LoadBalancerArn": {"Ref": "ApplicationLoadBalancer"},
"Port": "80",
"Protocol": "HTTP"
}
},
"ALBTargetGroup": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"HealthCheckIntervalSeconds" : 10,
"HealthCheckTimeoutSeconds" : 5,
"HealthyThresholdCount" : 2,
"Port" : 5000,
"Protocol" : "HTTP",
"UnhealthyThresholdCount" : 5,
"VpcId" : {"Ref" : "VPC"}
}
}
在CloudFormation上面Create Stack,用以上的模板,即可成功创建。创建完成后,访问ALB的DNS Name,即可看到当前提供Web服务的Hostname,多刷新几次,可以看到Hostname也会切换,证明了Web的访问可以成功通过ALB切换到不同的EC2进程中。
要远程登录到Web Server,可以通过跳板机来登录,命令如下ssh -p 22 -i ./id_rsa.pub -fNL 3307:mysql_ip:3306 root@root_ip