AWS CDKでOracle 19c構築

## この記事を読むとできること
以下構成に示すサービスをAWS CDK(TypeScript)で作成し、Linux(Amazon Linux 2023)とOracle DB(Oracle 19c)の疎通が確認できる

## 環境
- MacOS Sonoma 14.6.1（23G93）
    - zsh 5.9
    - aws-cli/2.9.12
    - cdk 2.149.0
    - node v20.11.1

## 構成
![ec2-rds-tutorial-architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258945/beebc13a-4056-a9e7-680c-c911bf807350.png)
出典：https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/tutorial-connect-ec2-instance-to-rds-database.html

## 構築サービスと詳細
- EC2
    - OS: Amazon Linux 2023 (以降AL2023)
    - インスタンスタイプ：M5.xlarge
    - パブリックサブネットに配置
    - 任意のIPからのHTTP, HTTPSおよびSSH接続を受け付ける
- RDS
    - DBMS: Oracle 19c
    - インスタンスタイプ：M5.large
    - プライベートサブネットに配置
    - EC2からのみ接続を受け付ける（ポート1521）

## スタック
```oracledb-test-stack.ts
import { CfnOutput, RemovalPolicy, Stack, StackProps, Token } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as rds from 'aws-cdk-lib/aws-rds';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';

export class OracledbTestStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    //VPC作成
    const vpc = new ec2.Vpc(this, 'OracleDBTestVPC', {
      ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
    })

    // EC2用キーペア作成
    const cfnKeyPair = new ec2.CfnKeyPair(this, 'CfnKeyPair', {
      keyName: 'oracledb-test-ssh-keypair',
    })
    cfnKeyPair.applyRemovalPolicy(RemovalPolicy.DESTROY)

    // EC2用キーペア取得コマンドアウトプット
    new CfnOutput(this, 'GetSSHKeyCommand', {
      value: `aws ssm get-parameter --name /ec2/keypair/${cfnKeyPair.getAtt('KeyPairId')} --region ${this.region} --with-decryption --query Parameter.Value --output text`,
    })
    //EC2 インスタンスの宣言
    const webServer1 = new ec2.Instance(this, "OracleDBTestServer1", {
      vpc,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
      machineImage: new ec2.AmazonLinuxImage({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
      }),
      vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
      keyName: Token.asString(cfnKeyPair.ref),
    });

    // インターネット接続およびサーバ設定のためのSSH接続を, 全ての IP アドレスからのアクセスを許可
    webServer1.connections.allowFromAnyIpv4(ec2.Port.tcp(80));
    webServer1.connections.allowFromAnyIpv4(ec2.Port.tcp(443));
    webServer1.connections.allowFromAnyIpv4(ec2.Port.tcp(22));
    
    // EC2 インスタンスアクセス用の IP アドレスを出力
    new CfnOutput(this, "OracleDBTestServer1PublicIPAddress", {
      value: `http://${webServer1.instancePublicIp}`,
    });
    
    // RDS用シークレット生成
    const databaseCredentialsSecret = new Secret(this, 'AppDbCredentialsSecret', {
      secretName: id + '-rds-credentials',
      generateSecretString: {
        secretStringTemplate: JSON.stringify({
          username: 'admin',
        }),
        excludePunctuation: true,
        includeSpace: false,
        passwordLength: 30,
        generateStringKey: 'password',
      },
    });
    // RDS のインスタンスを宣言
    const dbServer = new rds.DatabaseInstance(this, "OraTestDB", {
      vpc,
      // DatabaseInstanceEngine クラスを利用してデータベースエンジンを設定
      engine: rds.DatabaseInstanceEngine.oracleSe2({ version: rds.OracleEngineVersion.VER_19_0_0_0_2024_04_R1 }),
      // RDS DB インスタンスのインスタンスタイプを設定
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
      // RDS DB インスタンスのデータベース名を設定
      databaseName: "oratest",
      // RDS DB インスタンスにライセンスを指定
      licenseModel: rds.LicenseModel.LICENSE_INCLUDED,
      //シークレットをRDSに割り当て
      credentials: rds.Credentials.fromSecret(databaseCredentialsSecret),
    });
    
    // WebServer からのアクセスを許可
    dbServer.connections.allowDefaultPortFrom(webServer1);

  }
}
```
CDK deploy後（デプロイ方法等詳細は[ここ](https://catalog.workshops.aws/typescript-and-cdk-for-beginner/ja-JP)を参照）、LinuxにSSH接続し以下を行う

### Linux設定
```
# timedatectl status
Local time: Fri 2024-08-16 01:42:53 UTC
Universal time: Fri 2024-08-16 01:42:53 UTC
RTC time: Fri 2024-08-16 01:42:54
Time zone: n/a (UTC, +0000)
NTP enabled: yes
NTP synchronized: yes
RTC in local TZ: no
DST active: n/a
# timedatectl set-timezone Asia/Tokyo
# localectl status
System Locale: LANG=en_US.UTF-8
VC Keymap: n/a
X11 Layout: n/a
# localectl set-locale LANG=ja_JP.utf8
# source /etc/locale.conf
#
```
### DBクライアントツールインストール
[ここ](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.Oracle.html#CHAP_GettingStarted.Connecting.Oracle)をの3-9を参照
### クライアントツール起動確認
```
$ sqlplus admin@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=xxxx.rds.amazonaws.com)(PORT=1521)))(CONNECT_DATA=(SID=TEST)))

SQL*Plus: Release 19.0.0.0.0 - Production on Fri Aug 16 13:43:04 2024
Version 19.20.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

Enter password:

Connected to:
Oracle Database 19c Standard Edition 2 Release 19.0.0.0.0 - Production
Version 19.23.0.0.0

SQL>
```
LinuxとOracle DBが疎通できることを確認できた！

## 注意点（つまったところ）
- EC2へのSSH接続のためのキーペア作成時、最初EC2への受け渡し方法がCDKでどうやるのかわからずマネコンからローカルにキーペア落としてSSH接続しようとしたが`Permission denied. `
    - EC2側に渡してないから当たり前ですね。。。気付くのに半日溶かした。
- RDSシークレット生成・RDSへの割り当てについて、[この記事](https://qiita.com/m-chan/items/f56e9300351f68fdda08)ではMySQLを使っておりパラメータが色々違ってこれだけでは足りなかった。シークレットをRDSに渡してなくて何時間か溶かした。
- DBインスタンスにライセンス割り当てについて。以下エラー出て`LicenseModel`を指定する必要があることに気づく。
```Resource handler returned message: "The LicenseModel parameter is required for configurations with multiple options. (Service: Rds, Status Code: 400, Request ID: b293284e-8017-4546-985f-ee23b484356b)" (RequestToken: 53fbb6fd-62e6-c637-9d94-ce74eaa29ac9, HandlerErrorCode: InvalidRequest)```
- RDSシークレット生成時のキーとバリューの値が間違ってた
    - usernameの値はOracle  DBの場合`admin` にしないといけないらしい。適当な値を入れていた
- エラー: ```Resource handler returned message: "The parameter MasterUserPassword is not a valid password because it is longer than 30 characters.```とのことで`passwordLength`を30文字に指定したらいけた
## 参考
- https://catalog.workshops.aws/typescript-and-cdk-for-beginner/ja-JP
- https://dev.classmethod.jp/articles/build-ec2-key-pair-with-aws-cdk/
- https://qiita.com/m-chan/items/f56e9300351f68fdda08
- https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_rds/LicenseModel.html
- https://www.oracle.com/jp/database/technologies/instant-client.html
- https://qiita.com/billtench/items/ac58466b693a01ad6464
- https://qiita.com/moroishisan/items/e987921581896e6884db
- https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_ConnectToOracleInstance.html

## 最後に
CDKでOracle DB構築してる記事がほとんど見つからず大変だったので、、誰かのお役に立てたら嬉しいです！