Compute Engine に MySQLをインストール【CentOS 7】

# はしがき

ネットにある記事を参考にしながら作業を行うと躓くことが多くありました。この記事では、Compute Engine上でMySQLが動き、ローカルからログインできるところまでの手順を記載します。

無料枠のCompute EngineでMySQLを使いたい人には参考になるかもしれません。

最後には、Go言語を使ったGCEのインスタンスの起動と停止、IPアドレス取得プログラムが書いてあります。

## 環境

* 2024年4月17日
* Mac OS 14.0 (Intel Mac)
* Google Chrome

# 手順

## Compute Engineのインスタンスを作成

有効になっていない場合は、Compute Engine APIを有効化

* リージョン：us-west1
* ゾーン：us-west1-b

* マシンタイプ：e2-micro
* ブートディスク：
    * OS：CentOS
    * バージョン：CentOS 7
    * ブートディスクの種類：標準永続ディスク
    * サイズ：30GB

* 「詳細オプション」→「ネットワーキング」から見られる、「ネットワークタグ」に好きなタグ名を入力。（これはファイアウォール設定で使用します）

無料枠の構成です。

## ファイアウォールでTCP 3306を許可

「VPCネットワーク」→「ファイアウォール」から「ファイアウォール ルールを作成」に進む。

* 名前：<任意の名前>
* トラフィックの方向：上り
* 一致したときのアクション：許可
* ターゲットタグ：<上で決めたネットワークタグ>
* 送信元 IPv4 範囲：お試しで使用するなら `0.0.0.0/0` 
* プロトコルとポート
    * 指定したプロトコルとポート
        * TCP：（チェックをつける）
            * ポート：3306

## Compute EngineにSSHで接続

接続後、シェルで下記のコマンドを上から実行

```bash
sudo yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpm

# 鍵がないと怒られるため実行
sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023

sudo yum install -y mysql-community-server

sudo systemctl start mysqld.service

sudo systemctl enable mysqld.service

# 下記コマンドでパスワードが表示されるのでメモ
sudo grep 'temporary password' /var/log/mysqld.log

# 下記コマンドは、上記で表示されたパスワードを最初に入力する
# その後、任意のパスワードを入力したり、質問に回答したりする
mysql_secure_installation

# MySQLにログイン（パスワードは先ほど決めたもの）
mysql -u root -p

```

下記では、全ての権限を付与しています。

```sql
CREATE USER '<任意のユーザ名>' IDENTIFIED BY '<任意のパスワード>';

GRANT ALL ON *.* TO '<上と同じユーザ名>';

exit;
```

MySQLから抜けます。

```bash
sudo firewall-cmd --add-service=mysql --zone=public --permanent

sudo firewall-cmd --list-services --zone=public  --permanent
```

上記で外部からの接続を許可します。

## ローカルから接続

```bash
# 作成したユーザでログイン
mysql -h <Compute Engineインスタンスの外部IPアドレス> -P 3306 -u <作成したユーザ名> -p
```

# おまけ

下のような関数を利用すると、

* インスタンスの起動
* IPアドレスの取得
* MySQLにアクセスして処理
* インスタンスを停止

のようなことが一つのプログラムでできます。

環境変数`GOOGLE_APPLICATION_CREDENTIALS`には、サービスアカウントのJSONキーへのパスを書いておきます。

必要な権限は`compute.instances.get`、`compute.instances.start`、`compute.instances.stop`の三つです。下記プログラムを実行する場合は、「IAMと管理」から「ロール」、「ロールを作成」で新しくロールを作成するか、既存のそれっぽいロールをサービスアカウントに付与します。

言語はGoです。

```golang
package main

import (
	"context"
	"fmt"
	"os"

	compute "cloud.google.com/go/compute/apiv1"
	"cloud.google.com/go/compute/apiv1/computepb"
	"google.golang.org/api/option"
)

// Compute Engineのインスタンスを起動する
func StartInstance(projectID, zone, instanceName string) error {
	ctx := context.Background()
	sa := option.WithCredentialsFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
	instancesClient, err := compute.NewInstancesRESTClient(ctx, sa)
	if err != nil {
		return fmt.Errorf("NewInstancesRESTClient: %w", err)
	}
	defer instancesClient.Close()
	req := &computepb.StartInstanceRequest{
		Project:  projectID,
		Zone:     zone,
		Instance: instanceName,
	}
	op, err := instancesClient.Start(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to start instance: %w", err)
	}
	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}
	fmt.Println("Instance started.")
	return nil
}

// 外部ipアドレスを取得する
func GetIPAddress(projectID, zone, instanceName string) (string, error) {
	ctx := context.Background()
	sa := option.WithCredentialsFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
	instancesClient, err := compute.NewInstancesRESTClient(ctx, sa)
	if err != nil {
		return "", fmt.Errorf("NewInstancesRESTClient: %w", err)
	}
	defer instancesClient.Close()
	req := &computepb.GetInstanceRequest{
		Project:  projectID,
		Zone:     zone,
		Instance: instanceName,
	}
	resp, err := instancesClient.Get(ctx, req)
	if err != nil {
		return "", fmt.Errorf("unable to get instance: %w", err)
	}
	ip := *resp.NetworkInterfaces[0].AccessConfigs[0].NatIP
	return ip, nil
}

// Compute Engineのインスタンスを停止する
func StopInstance(projectID, zone, instanceName string) error {
	ctx := context.Background()
	sa := option.WithCredentialsFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
	instancesClient, err := compute.NewInstancesRESTClient(ctx, sa)
	if err != nil {
		return fmt.Errorf("NewInstancesRESTClient: %w", err)
	}
	defer instancesClient.Close()
	req := &computepb.StopInstanceRequest{
		Project:  projectID,
		Zone:     zone,
		Instance: instanceName,
	}
	op, err := instancesClient.Stop(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to stop instance: %w", err)
	}
	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}
	fmt.Println("Instance stopped.")
	return nil
}
```

#### 参考

https://qiita.com/HirotakaKikushima/items/fd225966ac1444db0c0f

https://cloud.google.com/compute/docs/instances/create-start-instance?hl=ja#go