スマートホームお引越しログ
はじめに
我が家ではAlexa、Switch Botその他諸々の連携をラズパイに入れたNode-REDで実装していたのですが、つい先日ラズパイが煙を吹いて動かなくなりました。
そんな訳で半強制的にNode-REDのお引越しをしたので、構成検討〜引っ越し完了までにやったことを備忘録がてら書いていこうと思います。
コスト・運用観点での構成検討
構成について今回検討したポイントは以下のとおりです。
オンプレ vs クラウド?
HW費1.5万、電気代月110円でラズパイを稼働させるのではなく、適当なクラウドサービスを月100円程度で使えた方がCapEx/OpEx両面で優れるため、クラウドサービスを利用することにしました。ここ数年で半導体不足と円安も手伝いラズパイも値上がりが著しいので、今後もHW費は値上がりすると考えると十分ペイすると判断しました。
お金と手間をかけずに移行したい(でも最低限セキュリティは担保したい)
コスト観点および移行の容易さから、VM1台で構成される簡素なシステムとしました。
当たり前ですがVMにPublic IPは付与せず、VMへのアクセスは踏み台サービスを利用することにしました。
バックアップについてはインフラ構成+Node-RED関連の設定ファイルのみGitHubで管理するものとし、全て壊れたらイチから再構築するものとしました。お金ないからね。
Raspbianからの移行が楽ちんなのでNode-REDの実行基盤には同じDebian系のUbuntuを採用するものとしました。
Node-REDのGUIにアクセスするため、Windows Serverを1台最低スペックで構築するものとしました。お金かかるけどしゃーない。。。
クラウドを極力タダで使いたい
一昔前ならHerokuが候補に挙がりましたが、Herokuの無償枠が昨年廃止されたのもあり、AWS/Azure/GCP/OCIと大手どころで検討しました。
AWS/AzureはAlways Free枠にVMが含まれていないため、GCPかOCIの二択で考えました。手持ちのGCPアカウントの無償枠が切れていたので、検証+初期構築のコストをケチるため、OCIを選択しました。
移行後の構成
以上を踏まえ、こんな構成でOracle Cloud上にNode-REDサーバを立てることにしました。リージョンについては東京だとVMをデプロイできない可能性があるらしいので、適当にサンノゼを選びました。
お引越し開始
インフラ構築
すでに手で立てたVCN/Subnet以外を以下のTerraformで構築しました。使ってみたい人は同じディレクトリに突っ込んでパラメータを書き換えた上で terraform plan && terraform apply してください。
※Terraformの初期設定やコードの作成にあたっては以下の記事を参考にしているので、設定値についてはこちらを参照してください
provider.tf: パラメータ+Oracle Cloud固有の設定値
variable "tenancy_ocid" {
default = "your tenancy ocid"
}
variable "user_ocid" {
default = "your user ocid"
}
variable "private_key_path" {
default = "your private key associated to user"
}
variable "fingerprint" {
default = "your fingerprint"
}
variable "region" {
default = "us-sanjose-1"
}
variable "vmShapeLinux" {
type = string
default = "VM.Standard.E2.1.Micro"
description = "VM shape"
}
variable "vmShapeWin" {
type = string
default = "VM.Standard.E5.Flex"
description = "VM shape"
}
variable "controllerInstanceImage" {
type = string
default = "ocid1.image.oc1.us-sanjose-1.aaaaaaaalwcfglbcq65ckrfdwgtk4kbtkedv4hm44qo7t3mos3krf3mhygnq"
description = "OCID of VM image"
}
variable "winInstanceImage" {
type = string
default = "ocid1.image.oc1.us-sanjose-1.aaaaaaaac2ujcnwrif63xvw2pazjg7y2jqxiihhe2rpdxalsvpazgbocdnhq"
description = "OCID of VM image of Bastion"
}
variable "privateSubnetId" {
type = string
default = "your private subnet ocid"
description = "Subnet Id on which VM is deployed."
}
variable "publicSubnetId" {
type = string
default = "your public subnet ocid"
description = "Subnet Id on which VM is deployed."
}
variable "linuxStatus" {
type = string
default = "RUNNING"
validation {
condition = var.linuxStatus != "RUNNING" || var.linuxStatus != "STOPPED"
error_message = "linuxStatus must be \"RUNNING\" or \"STOPPED\"."
}
}
variable "winStatus" {
type = string
default = "STOPPED"
validation {
condition = var.winStatus != "RUNNING" || var.winStatus != "STOPPED"
error_message = "winStatus must be \"RUNNING\" or \"STOPPED\"."
}
}
provider "oci" {
tenancy_ocid = var.tenancy_ocid
user_ocid = var.user_ocid
private_key_path = var.private_key_path
fingerprint = var.fingerprint
region = var.region
}
availability-domain.tf: 可用性ドメインの設定
data "oci_identity_availability_domains" "ads" {
compartment_id = var.tenancy_ocid
}
compute.tf:VMの設定
resource "oci_core_instance" "smarthouse-controller" {
# Required
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
compartment_id = var.tenancy_ocid
shape = var.vmShape
source_details {
source_id = var.controllerInstanceImage
source_type = "image"
}
# Optional
display_name = "smarthouse-controller"
create_vnic_details {
assign_public_ip = false
subnet_id = var.privateSubnetId
}
metadata = {
ssh_authorized_keys = file(var.sshPubKey)
}
preserve_boot_volume = false
agent_config {
is_management_disabled = false
is_monitoring_disabled = false
plugins_config {
desired_state = "ENABLED"
name = "Bastion"
}
}
state = var.linuxStatus
}
resource "oci_core_instance" "smarthouse-setting" {
# Required
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
compartment_id = var.tenancy_ocid
shape = var.vmShape
source_details {
source_id = var.winInstanceImage
source_type = "image"
}
# Optional
display_name = "smarthouse-setting"
shape_config {
baseline_ocpu_utilization = "BASELINE_1_8"
ocpus = 1
}
create_vnic_details {
assign_public_ip = false
subnet_id = var.privateSubnetId
}
launch_options {
boot_volume_type = "PARAVIRTUALIZED"
firmware = "UEFI_64"
is_pv_encryption_in_transit_enabled = "true"
network_type = "PARAVIRTUALIZED"
remote_data_volume_type = "PARAVIRTUALIZED"
}
preserve_boot_volume = false
agent_config {
is_management_disabled = false
is_monitoring_disabled = false
plugins_config {
desired_state = "ENABLED"
name = "Bastion"
}
}
state = var.winStatus
}
bastion.tf:BastionとBastion Sessionの設定
data "oci_core_instance" "smarthouse-controller" {
instance_id = oci_core_instance.smarthouse-controller.id
}
data "oci_core_instance" "smarthouse-setting" {
instance_id = oci_core_instance.smarthouse-setting.id
}
data "oci_core_subnet" "smarthouse-private-subnet" {
subnet_id = var.privateSubnetId
}
resource "oci_bastion_bastion" "smarthouse-controller-bastion" {
#Required
bastion_type = "STANDARD"
compartment_id = var.tenancy_ocid
target_subnet_id = var.privateSubnetId
#Optional
name = "smarthouse-controller-bastion"
client_cidr_block_allow_list = ["0.0.0.0/0"]
dns_proxy_status = "ENABLED"
max_session_ttl_in_seconds = 7200
}
resource "oci_bastion_session" "smarthouse-controller-session" {
#Required
bastion_id = oci_bastion_bastion.smarthouse-controller-bastion.id
key_details {
#Required
public_key_content = file(var.sshPubKey)
}
target_resource_details {
#Required
session_type = "MANAGED_SSH"
#Optional
target_resource_id = data.oci_core_instance.smarthouse-controller.id
target_resource_operating_system_user_name = "ubuntu"
}
#Optional
display_name = "smarthouse-controller-bastion-session"
session_ttl_in_seconds = 7200
}
resource "oci_bastion_session" "smarthouse-setting-session" {
#Required
bastion_id = oci_bastion_bastion.smarthouse-controller-bastion.id
key_details {
#Required
public_key_content = file(var.sshPubKey)
}
target_resource_details {
#Required
session_type = "PORT_FORWARDING"
#Optional
target_resource_id = data.oci_core_instance.smarthouse-setting.id
target_resource_port = "3389"
}
#Optional
display_name = "smarthouse-controller-bastion-session"
session_ttl_in_seconds = 7200
}
outputs.tf: 最後にNode-REDサーバへのsshコマンドとWindows Serverにsshトンネルを張るためのコマンドを表示する用。
output "ssh-command-for-linux" {
value = oci_bastion_session.smarthouse-controller-session.ssh_metadata.command
}
output "ssh-command-for-windows-rdp-tunnel" {
value = oci_bastion_session.smarthouse-setting-session.ssh_metadata.command
}
Node-REDの設定
Node-REDのインストール、ファイアーウォールの穴あけは以下のセットアップスクリプトをNode-REDサーバ上で実行しました。
# coding: utf-8
set -eu
## install required packages
sudo apt install nodejs npm ufw
## firewall settings
sudo ufw allow 22
sudo ufw allow 1880
sudo ufw enable
sudo ufw reload
## install node-red
## note: node-red requires Node.js 20.x (OS default: 10.x)
sudo npm install -g --unsafe-perm node-red
sudo npm install -g n
sudo n stable
sudo apt purge nodejs npm
cd /usr/local/lib/
sudo npm rebuild
## auto starting setting for node-red
sudo npm install -g pm2
sudo pm2 start $(which node-red) -- -v
sudo pm2 save
sudo pm2 startup systemd
フローの作成
Node-REDサーバを再起動したら、WindowsServerにRDPしてNode-REDのGUIを開いてフローを構築していきます。
本当はflows.jsonをそのままインポートしたかったのですが、バックアップを取る前にラズパイがおシャカになったのでイチからノードを構築し直しました。。。必要なパレットを再インストールしたり、Alexa Home Skill BridgeのログインIDを思い出したり、Alexaで要らないデバイスを削除したりSwitchBotアプリでトークンを再発行してConfに登録し直さなアカンのが地味にめんどくさい。
引っ越しを終えて
最近仕事がパワポ職人化したのもあり、コードを書いたりインフラを構築する機会も減っていたのでいい脳トレになったと思います。こういう機会はちょこちょこ確保したいですね。
OCIについては、とにかくAlways Free Tierの存在がありがたかったです。AWS/AzureだとVMがタダじゃなかったりするのでお財布には優しい。VM1台で完結する簡単なシステムを立てるだけならGCPかOCIで良いんじゃないですかね。
また、OCIでは独自のIaCツール(CloudFormationとかARM TemplateとかCloud Development Managerみたいな)を作らず、Terraform対応だけしているのは開発者目線で考えると賢い気がしてます。すでにTerraformでマルチクラウド管理をしているユーザーにとっては親和性が良さそうなので。AWSとGCPを併用している人、メッチャいますよね?
最後に一言。
いつまでも 動くと思うな 自宅のラズパイ
皆さんは構成情報やインストールスクリプトやコンフィグはちゃんとこまめにバックアップを取りましょうね。そうじゃないとお引越しにめっちゃ時間かかるので……。