フォーム読み込み中
皆さまこんにちは、ソフトバンクの周です。
この記事では非エンジニアの方々でも使えるTerraform実行ポータルを作ってみましたのでご紹介させていただきます。
本記事は"Alibaba Cloud サーバレスで作るTerraform実行ポータル"記事の後編になります。前編記事もあわせてご覧下さい。
次にデプロイするTerraformコードを作成していきます。本記事では全体のアーキテクチャを中心にご紹介するため、デプロイする環境はVPCのみの簡単な構成となります。
コードではterraform stateファイルは前編で作成した"backend-bucket-02"に格納するように指定しています。
mkdir backend && cd backend
touch main.tf
main.tfに以下のコードを貼り付けます。
variable "access_key" {}
variable "secret_key" {}
variable "region" { default = "ap-northeast-1" }
terraform {
backend "oss" {
}
}
// alibaba cloud provider
provider "alicloud" {
access_key = var.access_key
secret_key = var.secret_key
region = var.region
}
// create vpc
resource "alicloud_vpc" "default" {
vpc_name = "default"
cidr_block = "192.168.1.0/24"
}
次にバックエンドのコードを作成していきます。本記事では`Node.js` + `Express`で作成していきます。
作成するディレクトリは`main.tf`と同じbackendフォルダになります。
npm init -y
npm i express dotenv child_process cors
touch server.js
`server.js` ファイルが作られていますので、以下のソースコードを貼り付けます。
`/create`と`/destroy`APIを作成しており、それぞれ`child_process`で`terraform apply`と`terraform destroy`を実行しています。
実行に必要アクセスキーおよびシークレットキーは環境によって異なりますので、フロントから入力された値を使用します。
また、バケットにstateファイルを格納するため、コード内でアクセスキーの設定をお願いします。
'use strict';
const express = require('express');
const { execSync } = require('child_process')
require('dotenv')
// Constants
const PORT = 8080;
const HOST = '0.0.0.0';
const cors = require('cors')
const app = express();
const env = process.env
const BUCKET = 'state-bucket-03';
const ACCESS = 'Your Access Key'; // アクセスキーの設定、手動で設定します。
const SECRET = env.SECRET; // シークレットキーの設定、Function Computeから取得します。
const MOUNTPATH = '/app/backend';
app.use(express.json())
app.use(cors())
app.post('/create', (req, res) => {
const accessKey = req.body.access_key;
const secretKey = req.body.secret_key;
const head = accessKey.slice(0, 8);
const config = `bucket = \"${BUCKET}\"
key = \"${head}/terraform.tfstate\"
access_key = \"${ACCESS}\"
secret_key = \"${SECRET}\"
region = \"ap-northeast-1\"`;
const tfvars = `access_key = \"${accessKey}\"
secret_key = \"${secretKey}\"`;
execSync(`echo '${config}' > ${MOUNTPATH}/${head}.tfbackend`, ['-e']);
execSync(`echo '${tfvars}' > ${MOUNTPATH}/${head}.tfvars`, ['-e']);
execSync(`terraform init -backend-config="${MOUNTPATH}/${head}.tfbackend"`);
const stdout = execSync(`terraform apply -no-color -auto-approve -var-file="${MOUNTPATH}/${head}.tfvars"`)
execSync(`rm ${MOUNTPATH}/${head}.tfvars`);
res.send(stdout);
});
app.post('/destroy', (req, res) => {
const accessKey = req.body.access_key;
const secretKey = req.body.secret_key;
const head = accessKey.slice(0, 8);
const config = `bucket = \"${BUCKET}\"
key = \"${head}/terraform.tfstate\"
access_key = \"${ACCESS}\"
secret_key = \"${SECRET}\"
region = \"ap-northeast-1\"`;
const tfvars = `access_key = \"${accessKey}\"
secret_key = \"${secretKey}\"`;
execSync(`echo '${config}' > ${MOUNTPATH}/${head}.tfbackend`, ['-e']);
execSync(`echo '${tfvars}' > ${MOUNTPATH}/${head}.tfvars`, ['-e']);
execSync(`terraform init -backend-config="${MOUNTPATH}/${head}.tfbackend"`);
const stdout = execSync(`terraform destroy -no-color -auto-approve -var-file="${MOUNTPATH}/${head}.tfvars"`)
execSync(`rm ${MOUNTPATH}/${head}.tfvars`);
res.send(stdout);
});
app.listen(PORT, HOST, () => {
console.log(`Running on http://${HOST}:${PORT}`);
});
次に、作成したバックエンドとTerraformのコードをDockerイメージ化していきます。
作成するディレクトリは`main.tf`と同じbackendフォルダになります。
touch Dockerfile
`Dockerfile`ができていますので、以下のコードを貼り付けます。
FROM node:16.19
# install terraform
RUN wget https://releases.hashicorp.com/terraform/1.0.7/terraform_1.0.7_linux_amd64.zip && \
unzip terraform_1.0.7_linux_amd64.zip && \
mv terraform /usr/local/bin/ && \
rm terraform_1.0.7_linux_amd64.zip
WORKDIR /app
COPY package.json /app
COPY server.js /app
COPY main.tf /app
RUN npm install
EXPOSE 8080
CMD [ "node", "server.js" ]
最後に、フロントエンドのコードを作成します。本記事では`React`を使って作成します。
ルートディレクトリに移動後、`React`のアプリ作成と関連モジュールをインストールします。
npx create-react-app frontend
cd frontend
npm i @mui/material @emotion/react @emotion/styled react-terminal-ui axios
スタイリングには`Material-UI`と`react-terminal-ui`を使用しています。
`App.js`のコードは以下になります。
import React from 'react';
import { Box, AppBar, Toolbar, Typography, TextField, Button } from '@mui/material';
import Terminal, { ColorMode, TerminalOutput } from 'react-terminal-ui';
import axios from 'axios';
const Endpoint = 'Your Endpoint'; // Function Computeのエンドポイントを設定
function App() {
const [terminalLineData, setTerminalLineData] = React.useState([
<TerminalOutput></TerminalOutput>
]);
const [accessKey, setAccessKey] = React.useState('');
const [secretKey, setSecretKey] = React.useState('');
const [isDisable, setIsDisable] = React.useState(false);
const onDeploy = async () => {
try {
setIsDisable(true);
const response = await axios.post(`${Endpoint}/create`, {
access_key: accessKey,
secret_key: secretKey
}, {
headers: {
'Content-Type': 'application/json'
}
});
setTerminalLineData([
<TerminalOutput>{response.data}</TerminalOutput>
]);
} catch (error) {
setTerminalLineData([
<TerminalOutput>デプロイに失敗しました。</TerminalOutput>
]);
} finally {
setIsDisable(false);
}
}
const onRemove = async () => {
try {
setIsDisable(true);
const response = await axios.post(`${Endpoint}/destroy`, {
access_key: accessKey,
secret_key: secretKey
}, {
headers: {
'Content-Type': 'application/json'
}
});
setTerminalLineData([
<TerminalOutput>{response.data}</TerminalOutput>
]);
} catch (error) {
setTerminalLineData([
<TerminalOutput>デプロイに失敗しました。</TerminalOutput>
]);
} finally {
setIsDisable(false);
}
}
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar component="nav">
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1, display: { xs: 'none', sm: 'block' } }} >
デプロイポータル
</Typography>
</Toolbar>
</AppBar>
<Box sx={{ display: 'flex' }}>
<Box sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
flexDirection: 'column',
minWidth: '50vw'
}}>
<TextField
id="access-key"
label="アクセスキー"
variant="standard"
sx={{
paddingBottom: '10px',
width: '300px'
}}
value={accessKey}
onChange={(event) => setAccessKey(event.target.value)}
/>
<TextField
id="secret-key"
label="シークレットキー"
variant="standard"
type='password'
sx={{
paddingBottom: '40px',
width: '300px'
}}
value={secretKey}
onChange={(event) => setSecretKey(event.target.value)}
/>
<Box sx={{ display: 'flex' }}>
<Box>
<Button
variant="contained"
onClick={onDeploy}
disabled={accessKey === '' || secretKey === '' || isDisable}>
デプロイ
</Button>
</Box>
<Box sx={{ paddingLeft: '50px' }}>
<Button
variant="contained"
onClick={onRemove}
disabled={accessKey === '' || secretKey === '' || isDisable}>
削除
</Button>
</Box>
</Box>
</Box>
<Box sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh',
flexDirection: 'column',
minWidth: '45vw'
}}>
<Terminal
name='Deploy Status'
colorMode={ColorMode.Dark}
>
{terminalLineData}
</Terminal>
</Box>
</Box>
</Box>
);
}
export default App;
コード内でEndpointは前編で作成したFunction Computeのエンドポイントに書き換える必要があります。
作成したFunction Computeの関数から以下の部分を貼り付けてください。
以上で全てのコードの作成は完了になります。
まずは作成した`Dockerfile`を使って、`Terraform`と`Backend`のコードをDockerイメージ化します。
(MacOSのM1チップでビルドされたイメージは2023/3/15時点ではFunction Computeでサポートされていませんので、ご注意ください。)
cd ../backend
docker build --tag backend:latest .
ビルド後、イメージIDを確認しメモしておきます。
docker images | grep backend
先ほどメモしたイメージIDを置き換えて、下記コマンドでACRにプッシュします。
下記のサンプルでは東京リージョンを対象の手順となっていますので、ACRのリポジトリを別リージョンに作成した場合は、適宜リージョンを変更してください。
docker login registry-intl.ap-northeast-1.aliyuncs.com
docker tag <イメージID> registry-intl.ap-northeast-1.aliyuncs.com/portal-space/backend:latest
docker push registry-intl.ap-northeast-1.aliyuncs.com/portal-space/backend:latest
以上で、作成したバックエンド及びTerraformのコードのACRプッシュが完了しました。
次にACRにプッシュしたイメージをFunction Computeにデプロイしていきます。手順は下記の通りです。
以上でFunction ComputeへのDockerイメージデプロイは完了となります。
最後に、フロントエンドアプリケーションをOSSにデプロイしていきます。
フロントエンドをビルドし、buildフォルダが生成されていることを確認してください。
cd ./frontend
npm run build
OSSにアップロードする手順は以下になります。
以上で全ての作業は完了となります。
また、フロントエンドのエンドポイントはバケットの概要欄から確認することができますので、「インターネットアクセス」のバケットドメイン名をブラウザに入力していただくと、アプリケーションにアクセスできます。
本記事ではTerraformをウェブポータルから実行できる方法を紹介させていただきました。アーキテクチャをメインに紹介させていただきましたが、Terraformのテンプレートの変更やフロントエンドを作り込むことによって、もっと面白いアプリケーションができるかもしれません。
最後までご覧いただきありがとうございました。
Alibaba Cloudは中国国内でのクラウド利用はもちろん、日本-中国間のネットワークの不安定さの解消、中国サイバーセキュリティ法への対策など、中国進出に際する課題を解消できるパブリッククラウドサービスです。
条件に該当するページがございません