SQL Server FCIをFSx for Windowsを使って構築する(AWSリソース編)
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を修正
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