Terraformを使ってWiki.jsをAmazon RDSをデータベースとして動かしてみた
こんちには。
データアナリティクス事業本部 機械学習チームの中村(nokomoro3)です。
本記事ではTerraformを使ってWiki.jsをAmazon RDSをデータベースとして動かしてみたいと思います。
Wiki.jsとは
Wiki.jsはNode.js上で動作するJavaScriptで書かれたウィキエンジンです。
イメージとしては以下のような画面となります。
MarkdownやHTMLでウィキを記載することができ、画面上でページの差分が確認できるなど、ドキュメンテーションに必要な機能が豊富です。 ログイン周りや検索周りの統合機能も準備されています。
本記事のようにセルフホスティングも可能となっていますが、ライセンスが「GNU AGPL v3」となっている点はご留意ください。
Terraformの構成
コードは以下に公開しています。
今回は以下のような構成としています。少しStyle Guideからは外れるかもしれませんがご了承ください。
├─environments │ └─dev/ │ dev.tfvars │ main.tf └─modules ├─ec2/ │ config.yml │ main.tf ├─rds/ │ main.tf └─vpc/ main.tf
この中でdev.tfvars
だけは自身で設定が必要ですので、以下のような内容を入力してファイルを作成ください。
project_prefix="wikijs" rds_database="wiki" # RDSのデータベース名 rds_user="wikijs" # RDSのユーザ名 rds_password="" # RDSのパスワードを入力 wikijs_allow_ingress_cidr_block="" # Wiki.jsへのアクセスを許可するPCのIPアドレス範囲を入力
実装の詳細
実装の詳細を説明しつつ、参考にした情報やポイントを紹介します。
environments/dev/main.tf
mainのtfファイルです。
こちらでは特に何もしておらず、子モジュールを呼び出している形となります。
variable "project_prefix" {} variable "rds_database" {} variable "rds_password" {} variable "rds_user" {} variable "wikijs_allow_ingress_cidr_block" {} provider "aws" { default_tags { tags = { project_prefix = var.project_prefix } } } module "vpc" { source = "../../modules/vpc" project_prefix = var.project_prefix allow_ingress_cidr_block = var.wikijs_allow_ingress_cidr_block } module "ec2" { source = "../../modules/ec2" project_prefix = var.project_prefix public_subnet_id = module.vpc.subnet_id public_security_group_id = module.vpc.security_group_id rds_host = module.rds.rds_host rds_database = var.rds_database rds_password = var.rds_password rds_user = var.rds_user } module "rds" { source = "../../modules/rds" project_prefix = var.project_prefix private_subnet_ids = module.vpc.private_subnet_ids private_security_group_id = module.vpc.private_security_group_id rds_password = var.rds_password rds_database = var.rds_database rds_user = var.rds_user }
modules/vpc/main.tf
VPCのモジュールです。
variable "project_prefix" {} variable "allow_ingress_cidr_block" {} output "subnet_id" { value = aws_subnet.public_1a.id } output "security_group_id" { value = aws_security_group.sg.id } output "private_subnet_ids" { value = [ aws_subnet.private_1a.id, aws_subnet.private_1c.id ] } output "private_security_group_id" { value = aws_security_group.private_sg.id } // VPC resource "aws_vpc" "vpc" { cidr_block = "10.0.0.0/16" tags = { Name = var.project_prefix } } // インターネットゲートウェイ作成 resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = { Name = var.project_prefix } } // サブネット作成 (パブリック) resource "aws_subnet" "public_1a" { vpc_id = aws_vpc.vpc.id cidr_block = "10.0.0.0/24" availability_zone = "ap-northeast-1a" map_public_ip_on_launch = true tags = { Name = var.project_prefix } } // ルートテーブル作成とインターネットゲートウェイへのルート追加 resource "aws_route_table" "public" { vpc_id = aws_vpc.vpc.id tags = { Name = var.project_prefix } } // ルート resource "aws_route" "public" { route_table_id = aws_route_table.public.id gateway_id = aws_internet_gateway.igw.id destination_cidr_block = "0.0.0.0/0" } // サブネットにルートテーブルを関連付け resource "aws_route_table_association" "route_table_association" { subnet_id = aws_subnet.public_1a.id route_table_id = aws_route_table.public.id } // セキュリティグループ作成 resource "aws_security_group" "sg" { name = "${var.project_prefix}-sg" vpc_id = aws_vpc.vpc.id ingress { from_port = 3000 to_port = 3000 protocol = "tcp" cidr_blocks = ["${var.allow_ingress_cidr_block}"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } // サブネット作成 (private) resource "aws_subnet" "private_1a" { vpc_id = aws_vpc.vpc.id cidr_block = "10.0.1.0/24" availability_zone = "ap-northeast-1a" tags = { Name = var.project_prefix } } // サブネット作成 (private) resource "aws_subnet" "private_1c" { vpc_id = aws_vpc.vpc.id cidr_block = "10.0.2.0/24" availability_zone = "ap-northeast-1c" tags = { Name = var.project_prefix } } // セキュリティグループ作成 resource "aws_security_group" "private_sg" { name = "${var.project_prefix}-private-sg" vpc_id = aws_vpc.vpc.id ingress { from_port = 5432 to_port = 5432 protocol = "tcp" cidr_blocks = [ aws_subnet.public_1a.cidr_block ] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
VPCを新しく作成し、Publicサブネットを1つ、PrivateサブネットをRDS用に2つのAZで作成しています。
PublicにはWiki.jsが稼働するEC2を配置する予定ですので、このEC2用のSecurityGroupを、開発者マシンのIPアドレス範囲で、3000番のポートでIngressを許可しています。
またPrivateにはRDSを配置する予定ですので、RDS用のSecurityGroupを、Publicサブネットから5432番のポートでIngressを許可しています。
modules/rds/main.tf
RDSのモジュールです。
variable "project_prefix" {} variable "private_subnet_ids" {} variable "private_security_group_id" {} variable "rds_database" {} variable "rds_user" {} variable "rds_password" {} output "rds_host" { value = aws_db_instance.wikijs.address } resource "aws_db_subnet_group" "wikijs" { name = var.project_prefix subnet_ids = var.private_subnet_ids } resource "aws_db_parameter_group" "wikijs" { name = var.project_prefix family = "postgres15" } resource "aws_db_instance" "wikijs" { instance_class = "db.t3.micro" engine = "postgres" engine_version = "15.5" storage_type = "gp2" allocated_storage = 20 db_name = var.rds_database username = var.rds_user password = var.rds_password parameter_group_name = aws_db_parameter_group.wikijs.name identifier = var.rds_user vpc_security_group_ids = [var.private_security_group_id] db_subnet_group_name = aws_db_subnet_group.wikijs.name skip_final_snapshot = true deletion_protection = true lifecycle { prevent_destroy = false } }
特筆すべき点はそこまでないのですが、接続情報はEC2と共通で参照するためvariableにしています。
また削除保護が設定されているため、terraform destroy
のときに消えないようになっています。
modules/ec2/main.tf
Wiki.jsが稼働するEC2のモジュールです。
variable "project_prefix" {} variable "public_subnet_id" {} variable "public_security_group_id" {} variable "rds_host" {} variable "rds_database" {} variable "rds_user" {} variable "rds_password" {} output "config_content" { value = local.config_content } output "user_data" { value = aws_instance.main.user_data_base64 } # 信頼ポリシー data "aws_iam_policy_document" "ec2" { statement { effect = "Allow" principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } actions = ["sts:AssumeRole"] } } # IAMロール resource "aws_iam_role" "role" { name = "${var.project_prefix}-instance-role" assume_role_policy = data.aws_iam_policy_document.ec2.json managed_policy_arns = [ "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" ] } # インスタンスプロファイル resource "aws_iam_instance_profile" "instance_profile" { name = "${var.project_prefix}-instance-role-profile" role = aws_iam_role.role.name } data "local_file" "config_file" { filename = "${path.module}/config.yml" } locals { config_content = data.local_file.config_file.content } # EC2インスタンス resource "aws_instance" "main" { ami = "ami-031134f7a79b6e424" instance_type = "t3.small" subnet_id = var.public_subnet_id iam_instance_profile = aws_iam_instance_profile.instance_profile.name vpc_security_group_ids = [var.public_security_group_id] associate_public_ip_address = true user_data = <<-EOF #!/bin/bash # nodeのインストール yum install nodejs npm -y # wikijsのインストール mkdir -p /opt/wikijs cd /opt/wikijs wget https://github.com/Requarks/wiki/releases/latest/download/wiki-js.tar.gz mkdir wiki tar xzf wiki-js.tar.gz -C ./wiki cd ./wiki # RDSの証明書バンドルの取得 wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem # configファイルを実体化 echo "${local.config_content}" > config.yml # 環境変数 export DB_TYPE="postgres" export DB_HOST="${var.rds_host}" export DB_NAME="${var.rds_database}" export DB_PORT=5432 export DB_USER="${var.rds_user}" export DB_PASS="${var.rds_password}" # logrotateの設定 mkdir -p /var/log/wikijs/ echo "/var/log/wikijs/*.txt { daily rotate 7 compress delaycompress missingok notifempty create 640 root adm }" >/etc/logrotate.d/wikijs # wikijsの起動 node server 2>&1 | tee /var/log/wikijs/log.txt & EOF tags = { Name = "${var.project_prefix}" } }
必要なセットアップはユーザデータで行っています。
Wiki.js向けのインストール手順は以下を参考にしています。
RDSの証明書バンドルの取得がポイントで、こちらをWiki.jsの設定として読み込む必要があります。
これをしない場合、Wiki.jsからSSLでRDSに接続時する際にエラーとなります。
Wiki.jsの設定ファイルであるconfig.yml
はローカルに保存しておき、そちらをそのままユーザデータを使ってアップロードしています。
必要な接続情報はEC2の環境変数で与えられるように対応しています。config.yml
の詳細は後述します。
接続情報はterraformのvariableから環境変数に取得しており、本来はSecret Manager等をつかうべきかもしれませんが、今回は簡単に構築しています。
modules/ec2/config.yml
最後にWiki.jsの設定ファイルであるconfig.yml
の説明をします。
以下は必要箇所のみ抜粋しています。
###################################################################### # Wiki.js - CONFIGURATION # ####################################################################### # Full documentation + examples: # https://docs.requarks.io/install # --------------------------------------------------------------------- # Port the server should listen to # --------------------------------------------------------------------- port: 3000 # --------------------------------------------------------------------- # Database # --------------------------------------------------------------------- # Supported Database Engines: # - postgres = PostgreSQL 9.5 or later # - mysql = MySQL 8.0 or later (5.7.8 partially supported, refer to docs) # - mariadb = MariaDB 10.2.7 or later # - mssql = MS SQL Server 2012 or later # - sqlite = SQLite 3.9 or later db: type: \$(DB_TYPE) host: '\$(DB_HOST)' port: \$(DB_PORT) db: '\$(DB_NAME)' user: '\$(DB_USER)' pass: '\$(DB_PASS)' ssl: true # Optional - PostgreSQL / MySQL / MariaDB only: # -> Uncomment lines you need below and set `auto` to false # -> Full list of accepted options: https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options sslOptions: auto: false # rejectUnauthorized: false ca: /opt/wikijs/wiki/global-bundle.pem # cert: path/to/cert.crt # key: path/to/key.pem # pfx: path/to/cert.pfx # passphrase: xyz123 # Optional - PostgreSQL only: schema: public # SQLite only: storage: path/to/database.sqlite ####################################################################### # ADVANCED OPTIONS # ####################################################################### # Do not change unless you know what you are doing! # --- 以降省略 ---
db:
ブロックの設定は環境変数を参照するようにしていますが、Terraformで$
が解釈されないようにエスケープをしています。
またsslOptions:
ブロックのca:
で先ほどEC2のユーザデータで取得する証明書バンドルのパスを設定しています。
これとは別にssl:
ブロックがconfig.yml
には存在するのですが、こちらはWiki.js側のWebサーバーとしてのSSL設定なので混同されないようにされてください。
config.yml
の詳細は以下にも記載されています。
デプロイ
environments/dev
でコマンドを実行していきます。
initをまず実行します。
terraform init
次のapplyを実行すればOKです。
terraform apply -var-file dev.tfvars
動作確認
マネジメントコンソールからEC2インスタンスのPublic IPを確認します。
こちらに基づいて、`http://{確認したIPアドレス}:3000`にアクセスすると以下のような管理者アカウント作成画面へ遷移しますので、アドレスやパスワードを設定します。
次にログイン画面に遷移しますので、ログインします。
最初に何をするかの画面に遷移しますので、今回は「CREATE HOME PAGE」を選択します。
「Markdown」を選択します。
ページタイトルとページ説明を記載します。
内容を記載し、右上の「CREATE」を押下します。
すると以下のような画面が確認できます。
注意点
デフォルトでは未ログインユーザに対しても閲覧権限がある状態となりますので、問題がある場合は管理者画面からGuests Groupのread:pages
権限のチェックを外してご利用ください。
後片付け
以下でリソースを削除します。
terraform destroy -var-file dev.tfvars
RDSはマネジメントコンソールの方から削除されてください。
まとめ
如何でしたでしょうか。本記事がWiki.jsを使用される方の参考になれば幸いです。
参考
- [Terraform][CloudFormation]最新のAMI IDの取得方法 #AWS - Qiita
- Amazon EC2 での Amazon Linux 2023 - Amazon Linux 2023
- チュートリアル: Amazon EC2 インスタンスでの Node.js のセットアップ - AWS SDK for JavaScript
- Linux 環境に Node.js インストール #Node.js - Qiita
- AmazonSSMManagedInstanceCore - AWS マネージドポリシー
- PostgreSQL DB インスタンスを作成して接続する - Amazon Relational Database Service