この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWSでSQL Serverを利用する場合、マネージドなRDS for SQL Serverを利用することをオススメします。
しかし、さまざまな理由によってRDS for SQL Serverを採用できず、EC2上でSQL Serverを利用することもあります。
今回はそんなケースで、RDS for SQL Serverは利用できないが、SQL Server on EC2をマルチAZで動かしたい場合の選択肢となる、 SQL Server Always OnのFCI(フェールオーバークラスタインスタンス)を構築する機会があったので構築手順を紹介します。
2021/03/15追記
本ブログではAWSリソースのみ構築していきます。 Windows内部の構築についてはこちらのブログに書きましたのであわせて御覧ください。
参考サイト
このAWSブログを参考にSQL Server FCI環境を作ります。 本ブログではCloudFormationを使って、具体的なAWSリソースを構築していきます。
構成図
AWSの構成は次のような感じです。
SQL ServerのNodeとして使うEC2には、ENIを追加して3つのプライベートIPを付与します。 EC2はすべてこのブログを参考にCloudFormationでドメイン参加させます。
AWS環境を構築してみる
CloudFormationを使って前述の構成図の環境を構築します。 CloudFormationのテンプレートは長いので、ブログの最後に記します。
CFnテンプレートをアップロードして次へ進みます。
スタックの名前は適当につけます。今回は SQLServerFCISample という名前にしています。 各パラメーターは次のとおりに設定していきます。
- KeyName
- EC2のキーペアを選択します。
- MicrosoftAdDomainName
- Microsoft ADに設定するドメイン名を入力します。
- MicsoftAdPassword
- Microsoft ADに設定するパスワードを入力します。
- MyCidr
- Bastion用のEC2に接続可能なCIDRを指定します。設定したCIDRのみセキリュティグループでBastionサーバーへの接続を許可します。
- SsmParameterValueWindowsServer2016JapaneseFullBaseParameter
- Windows ServerのAMIが指定されているSSMパラメーターを指定します。特にデフォルトから変更する必要はありません。
- VpcCidrPrefix
- VPCのCIDRの第1,2オクテットを指定します。
スタックオプションの設定は特に必要ないので次へ進みます。
最後に設定を確認し、IAMの確認にチェックを入れてスタックの作成をします。
これでAWS環境の構築は完了です。
動作確認
まずはBationサーバーにリモートデスクトップで接続してみます。
BastionサーバーはElastic IPを付与しているので、パブリックIPをメモしておきます。
リモートデスクトップアプリを立ち上げて、先ほどメモしたパブリックIP宛に接続します。
EC2はCFnで既にドメイン参加してるので、ADユーザーでログインできるようになっています。 MicrosoftADの初期ユーザーである Admin ユーザーで接続できます。 パスワードは、CFnのパラメーターとして入力したパスワードと同じものです。
Bastionサーバーに接続できたら、次にSQL Server Node用のEC2に接続してみます。
SQL Server Node用のEC2は、プライベートIPを 10.22.10.11
(別AZのEC2は 10.22.11.11
)に固定しているので、そのIPにリモートデスクトップで接続します。
SQL Server Node用のEC2もADユーザーでログインできるようになっているので、同じ様にCFnのパラメーターとして入力したパスワードを入力します。
そうすると、SQL Server Node用のEC2へリモートデスクトップでアクセスできます。
次に、SQL Server Node用のEC2からFSx for Windowsで作成した共有ファイルサーバーへアクセスしてみます。
マネジメントコンソール画面から、作成したFSx for Windowsの DNS名 を確認します。
該当のネットワークドライブにアクセスして、新規テキストファイルを作ってみて、共有ファイルサーバーへ接続できることが確認できました。
(例:\fs-099d3b7de55175cf4.example.local\share)
(例:\amznfsxoinytskh.example.local\share)
終わりに
ここまで、FSx for Windowsを使ったSQL Server FCIのAWSリソースを構築しました。
次回は、このAWSリソースに対して、Windowsクラスターの構築とSQL ServerのインストールをしてSQL Server FCI環境を作っていきます。
CloudFormationテンプレート
※2021/03/15 SQL Server Node用のENIを修正
template.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: 'SQL Server FCI Sample'
Parameters:
SsmParameterValueWindowsServer2016JapaneseFullBaseParameter:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-windows-latest/Windows_Server-2016-Japanese-Full-Base
VpcCidrPrefix:
Type: String
Description: '(Example: 10.22)'
Default: 10.22
MicrosoftAdDomainName:
Type: String
Description: '(Example: example.local)'
Default: example.local
MicrosoftAdPassword:
Type: String
NoEcho: True
MyCidr:
Type: String
Description: '(Example: 192.0.2.100/32)'
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Default: cm-kitano-try
Resources:
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Sub '${VpcCidrPrefix}.0.0/16'
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-vpc'
VpcPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${VpcCidrPrefix}.0.0/24'
VpcId: !Ref Vpc
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet1'
VpcPublicSubnet1RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet1-routetable'
VpcPublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref VpcPublicSubnet1RouteTable
SubnetId: !Ref VpcPublicSubnet1
VpcPublicSubnet1DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VpcPublicSubnet1RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref VpcIGW
DependsOn:
- VpcVPCGW
VpcPublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${VpcCidrPrefix}.1.0/24'
VpcId: !Ref Vpc
AvailabilityZone: !Select
- 1
- !GetAZs
Ref: AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet1'
VpcPublicSubnet2RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet2-routetable'
VpcPublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref VpcPublicSubnet2RouteTable
SubnetId: !Ref VpcPublicSubnet2
VpcPublicSubnet2DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VpcPublicSubnet2RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref VpcIGW
DependsOn:
- VpcVPCGW
VpcPublicSubnet3:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${VpcCidrPrefix}.2.0/24'
VpcId: !Ref Vpc
AvailabilityZone: !Select
- 2
- !GetAZs
Ref: AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet3'
VpcPublicSubnet3RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet3-routetable'
VpcPublicSubnet3RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref VpcPublicSubnet3RouteTable
SubnetId: !Ref VpcPublicSubnet3
VpcPublicSubnet3DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VpcPublicSubnet3RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref VpcIGW
DependsOn:
- VpcVPCGW
VpcIGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-igw'
VpcVPCGW:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref Vpc
InternetGatewayId: !Ref VpcIGW
VpcPrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${VpcCidrPrefix}.10.0/24'
VpcId: !Ref Vpc
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-subnet1'
VpcPrivateSubnet1RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-subnet1-routetable'
VpcPrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref VpcPrivateSubnet1RouteTable
SubnetId: !Ref VpcPrivateSubnet1
VpcPrivateSubnet1DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VpcPrivateSubnet1RouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref VpcPublicSubnet2NATGateway
VpcPrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${VpcCidrPrefix}.11.0/24'
VpcId: !Ref Vpc
AvailabilityZone: !Select
- 1
- !GetAZs
Ref: AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-subnet1'
VpcPrivateSubnet2RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-subnet2-routetable'
VpcPrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref VpcPrivateSubnet2RouteTable
SubnetId: !Ref VpcPrivateSubnet2
VpcPrivateSubnet2DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VpcPrivateSubnet2RouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref VpcPublicSubnet2NATGateway
VpcPrivateSubnet3:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${VpcCidrPrefix}.12.0/24'
VpcId: !Ref Vpc
AvailabilityZone: !Select
- 2
- !GetAZs
Ref: AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-subnet3'
VpcPrivateSubnet3RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-private-subnet3-routetable'
VpcPrivateSubnet3RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref VpcPrivateSubnet3RouteTable
SubnetId: !Ref VpcPrivateSubnet3
VpcPrivateSubnet3DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VpcPrivateSubnet3RouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref VpcPublicSubnet2NATGateway
VpcPublicSubnetNATGatewayEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-natgateway-eip'
VpcPublicSubnet2NATGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt VpcPublicSubnetNATGatewayEIP.AllocationId
SubnetId: !Ref VpcPublicSubnet2
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-natgateway'
MicrosoftAD:
Type: AWS::DirectoryService::MicrosoftAD
Properties:
Name: !Ref MicrosoftAdDomainName
Password: !Ref MicrosoftAdPassword
VpcSettings:
SubnetIds:
- !Ref VpcPrivateSubnet1
- !Ref VpcPrivateSubnet2
VpcId: !Ref Vpc
Edition: Standard
DHCPOptions:
Type: AWS::EC2::DHCPOptions
Properties:
DomainName: !Ref MicrosoftAdDomainName
DomainNameServers: !GetAtt MicrosoftAD.DnsIpAddresses
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-dhcp-options'
VPCDHCPOptionsAssociation:
Type: AWS::EC2::VPCDHCPOptionsAssociation
Properties:
DhcpOptionsId: !Ref DHCPOptions
VpcId: !Ref Vpc
SqlServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub '${AWS::StackName}-sql-server-sg'
GroupName: !Sub '${AWS::StackName}-sql-server-sg'
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
SecurityGroupIngress:
- CidrIp: !GetAtt Vpc.CidrBlock
Description: allow all access from vpc
IpProtocol: "-1"
VpcId: !Ref Vpc
BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub '${AWS::StackName}-bastion-sg'
GroupName: !Sub '${AWS::StackName}-bastion-sg'
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
SecurityGroupIngress:
- CidrIp: !GetAtt Vpc.CidrBlock
Description: allow all access from vpc
IpProtocol: "-1"
- CidrIp: !Ref MyCidr
Description: allow all access from mycidr
FromPort: 0
IpProtocol: tcp
ToPort: 65535
VpcId: !Ref Vpc
Ec2IamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: ec2.amazonaws.com
Version: "2012-10-17"
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
- 'arn:aws:iam::aws:policy/AmazonSSMDirectoryServiceAccess'
RoleName: !Sub '${AWS::StackName}-iam-role'
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Ec2IamRole
Bastion:
Type: AWS::EC2::Instance
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
IamInstanceProfile: !Ref Ec2InstanceProfile
ImageId: !Ref SsmParameterValueWindowsServer2016JapaneseFullBaseParameter
InstanceInitiatedShutdownBehavior: stop
InstanceType: t3.small
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref BastionSecurityGroup
SsmAssociations:
- AssociationParameters:
- Key: directoryId
Value:
- !Ref MicrosoftAD
- Key: directoryName
Value:
- !Ref MicrosoftAdDomainName
DocumentName: AWS-JoinDirectoryServiceDomain
SubnetId: !Ref VpcPublicSubnet1
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-bastion'
BastionEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref Bastion
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-bastion-eip'
SqlServer1:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref Ec2InstanceProfile
ImageId: !Ref SsmParameterValueWindowsServer2016JapaneseFullBaseParameter
InstanceInitiatedShutdownBehavior: stop
InstanceType: t3.large
KeyName: !Ref KeyName
NetworkInterfaces:
- DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref SqlServerSecurityGroup
PrivateIpAddresses:
- Primary: true
PrivateIpAddress: !Sub '${VpcCidrPrefix}.10.11'
- Primary: false
PrivateIpAddress: !Sub '${VpcCidrPrefix}.10.12'
- Primary: false
PrivateIpAddress: !Sub '${VpcCidrPrefix}.10.13'
SubnetId: !Ref VpcPrivateSubnet1
SsmAssociations:
- AssociationParameters:
- Key: directoryId
Value:
- !Ref MicrosoftAD
- Key: directoryName
Value:
- !Ref MicrosoftAdDomainName
DocumentName: AWS-JoinDirectoryServiceDomain
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-sqlserver1'
SqlServer2:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref Ec2InstanceProfile
ImageId: !Ref SsmParameterValueWindowsServer2016JapaneseFullBaseParameter
InstanceInitiatedShutdownBehavior: stop
InstanceType: t3.large
KeyName: !Ref KeyName
NetworkInterfaces:
- DeleteOnTermination: true
DeviceIndex: 0
GroupSet:
- !Ref SqlServerSecurityGroup
PrivateIpAddresses:
- Primary: true
PrivateIpAddress: !Sub '${VpcCidrPrefix}.11.11'
- Primary: false
PrivateIpAddress: !Sub '${VpcCidrPrefix}.11.12'
- Primary: false
PrivateIpAddress: !Sub '${VpcCidrPrefix}.11.13'
SubnetId: !Ref VpcPrivateSubnet2
SsmAssociations:
- AssociationParameters:
- Key: directoryId
Value:
- !Ref MicrosoftAD
- Key: directoryName
Value:
- !Ref MicrosoftAdDomainName
DocumentName: AWS-JoinDirectoryServiceDomain
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-sqlserver2'
FSXSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub '${AWS::StackName}-fsx-sg'
GroupName: !Sub '${AWS::StackName}-fsx-sg'
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
SecurityGroupIngress:
- CidrIp: !GetAtt Vpc.CidrBlock
Description: allow all access from vpc
IpProtocol: "-1"
VpcId: !Ref Vpc
DataFSX:
Type: AWS::FSx::FileSystem
Properties:
FileSystemType: WINDOWS
SubnetIds:
- !Ref VpcPrivateSubnet1
- !Ref VpcPrivateSubnet2
SecurityGroupIds:
- !GetAtt FSXSecurityGroup.GroupId
StorageCapacity: 32
StorageType: SSD
WindowsConfiguration:
ActiveDirectoryId: !Ref MicrosoftAD
AutomaticBackupRetentionDays: 0
DeploymentType: MULTI_AZ_1
PreferredSubnetId: !Ref VpcPrivateSubnet1
ThroughputCapacity: 8
QuorumFSX:
Type: AWS::FSx::FileSystem
Properties:
FileSystemType: WINDOWS
SubnetIds:
- !Ref VpcPrivateSubnet3
SecurityGroupIds:
- !GetAtt FSXSecurityGroup.GroupId
StorageCapacity: 32
StorageType: SSD
WindowsConfiguration:
ActiveDirectoryId: !Ref MicrosoftAD
AutomaticBackupRetentionDays: 0
DeploymentType: SINGLE_AZ_1
ThroughputCapacity: 8