こんちには。
データアナリティクス事業本部 機械学習チームの中村(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