Wildcardな雑記

なんか書く

Go言語でデータベースを使う処理を書いてHeroku上に公開してみる手順の覚書

はじめに

Go言語でWebアプリを作ってみたいと思い,作ったWebアプリを簡単に公開できる方法について調べていた.まずは無料でできる方法から始めたかったので,そこでHerokuを使ってみることにした.

また,アプリを作る上でデータベースに関わる処理も必要になってくるので,データベースが関わるコードをHerokuで動作させるために必要な設定の仕方についても試してみることにした.

今回試すのは,取りあえずデータベースに適当なテーブルを作成し,そのデータを読み取ってHTMLに表示するだけのGo言語で書いたサーバーサイドプログラムである.Heroku上でデータベース連携の処理を実行するのは,アドオンを追加したり等ローカルで試すのとは勝手が異なるので,少しつまづきそうになった.手順を覚書として書いておく.

次節に手順を書く.

フォルダ構成

[~/Code/Project/proj_y2022/proj0102/jikken3]
% tree .
.
├── dbdata
│   └── setup.sql
└── main.go


1 directory, 2 files

Heroku上で実行されるサーバサイドのプログラム

main.go

今回注目するのはhellodb関数であり,これはURLで/dbのパスにアクセスしたときの処理について書いてある.ここで行なっているのは,テーブルから全てのレコードを読み取り,カラム(name)を改行で連結した文字列として出力する.

package main

import (
    "io"
    "net/http"
    "os"
    "log"
    "database/sql"
    _ "github.com/lib/pq"
    "strings"
)

var Db *sql.DB

func hello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Hello World!!")
}

type member struct {
    NUM string
    NAME string
}

func hellodb(w http.ResponseWriter, r *http.Request) {
    Db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
    defer Db.Close()
    if err != nil {
        log.Fatalf("Error opening database: %q", err)
    }

    rows, err := Db.Query("SELECT * FROM members")
    defer rows.Close()
    if err != nil {
        io.WriteString(w, "Errorrr!")
        return
    }

    var out string
    var names []string
    for rows.Next() {
        var e member
        rows.Scan(&e.NUM, &e.NAME)
        names = append(names, e.NAME)
    }
    out = strings.Join(names, "\n")
    io.WriteString(w, out)
}

func main() {
    port := os.Getenv("PORT")
    http.HandleFunc("/", hello)
    http.HandleFunc("/db", hellodb)
    http.ListenAndServe(":"+port, nil)
}

setup.sql

上記のGo言語のプログラム(main.go)からアクセスされることになる,今回のサンプルとしてのテーブルを定義する.テーブル(members)は名前(name)と番号(id)をレコードとして保存する構成であり,2つのカラム(id, name)を持つ.

[~/Code/Project/proj_y2022/proj0102/jikken3]
% cat dbdata/setup.sql
CREATE TABLE members (
  id integer,
  name varchar(30)
);
INSERT INTO members VALUES (1, 'John Smith');
INSERT INTO members VALUES (2, 'Richard Roe');
INSERT INTO members VALUES (3, 'Baby Doe');

Herokuにデプロイするまでの流れ

流れとしては以下である.詳細は次になる.

  • (1) リポジトリを作る
  • (2) Heroku上にアプリを作成する
  • (3) Herou上のアプリにデータベースを追加する
  • (4) 作成したコードをHeroku上のリポジトリにpushする
  • (5) Heroku上のアプリのデータベースにテーブルを作成する

(1) リポジトリを作る

[~/Code/Project/proj_y2022/proj0102/jikken3]
% git init

同じディレクトリ内で以下を実行する.go mod initで作成されるgo.modファイルはGoモジュールの依存関係を記述したファイルであり,これはHeroku上でのビルドにおいて使用される.

% go mod init github.com/<ユーザ名>/jikken3
% go mod tidy
% go mod vendor

(2) Heroku上にアプリを作成する

heroku createコマンドでアプリの名前を指定できる.(とりあえずjohnapp47としておく)

[~/Code/Project/proj_y2022/proj0102/jikken3]
% heroku create johnapp47

(3) Herou上のアプリにデータベースを追加する

今回のコードはpostgresqlを使うので,以下のようにしてHerokuアプリにアドオンを追加する.

% heroku addons:create heroku-postgresql:hobby-dev --app johnapp47

(4) 作成したコードをHeroku上のリポジトリにpushする

[~/Code/Project/proj_y2022/proj0102/jikken3]
% git add -A .

% git commit -m "test commit"

% git push heroku  master

(5) Heroku上のアプリのデータベースにテーブルを作成する

Herokuアプリ(今回はjohnapp47)の上に展開された,Go言語のサーバーサイドプログラムから利用するデータベースに対して,先に挙げたsetup.sqlSQLを実行し,テーブルを作成する.

まず確認として,ファイル群は先ほどpushしたので,setup.sqlは以下のようにして認識されている.

[~/Code/Project/proj_y2022/proj0102/jikken3]
% heroku run bash --app johnapp47
Running bash on ⬢ johnapp47... up, run.8167 (Free)
~ $ ls
Procfile  bin  dbdata  go.mod  go.sum  main.go  vendor
~ $ ls dbdata
setup.sql
~ $

それでは,テーブルを作成するためにsetup.sqlの中身のSQL文をデータベース上で実行する. HerokuのPostgresサーバにのコンソールにアクセスするにはheroku pg:psql --app <アプリ名>と実行すればよい.

[~/Code/Project/proj_y2022/proj0102/jikken3]
% heroku pg:psql --app johnapp47
--> Connecting to postgresql-convex-34468
psql (14.1, server 13.5 (Ubuntu 13.5-2.pgdg20.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

johnapp47::DATABASE=>

ファイル(setup.sql)を読み込んで実行するコマンドは\i <ファイル名>

johnapp47::DATABASE=> \i dbdata/setup.sql
CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1
johnapp47::DATABASE=> \dt
             List of relations
 Schema |  Name   | Type  |     Owner
--------+---------+-------+----------------
 public | members | table | jutsizbfgorver
(1 row)

テーブルが作成されたのをselect文で確認.

johnapp47::DATABASE=> select * from members;
 id |    name
----+-------------
  1 | John Smith
  2 | Richard Roe
  3 | Baby Doe
(3 rows)


johnapp47::DATABASE=> \q

デプロイされたアプリにアクセスしてみる

https://johnapp47.herokuapp.com/dbにアクセスすると,以下のような表示を確認.

f:id:aster-wildcard:20220113203127p:plain

Go言語で書いたサーバプログラムがデータベースからレコードを読み込んでテーブル(members)から読んだ名前の一覧を表示できたことを確認できた.

おわりに

Heroku上でデータベースが連携するコードをデプロイする方法について理解できた.個人的にDockerを学び始めたこともあり,この後にDocker on Herokuなる存在を知ったので次はこっちも試してみようと思う.

参考資料

Docker(docker-compose)でPostgreSQLの環境構築の覚書

はじめに

Dockerを用いたPostgreSQLの環境構築について試したときのメモを書いておく.

以下を参考にした

やったこと

DockerでPostgreSQLサーバをコンテナとして起動して,クライアントプログラム(psql)からデータベースにアクセスしたりをやってみた.また,イメージの初回起動時に事前に.sqlファイルに定義したテーブルが作成されるようにしたりした.

環境構築の前に

以下を実行した.postgresのベースとなるイメージの取得と,PostgreSQLのクライアントの方のインストールを行う.

% docker pull postgres:14-alpine

クライアントが欲しい場合はlibpqをinstallする.

% brew install libpq

フォルダ構成

以下のような構成から成る.

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% tree -a .
.
├── docker-compose.yml
└── postgres
    ├── .env
    ├── Dockerfile
    └── db
        └── intidb.sql

2 directories, 4 files

各ファイルについて

docker-compose.yml

今回はローカルマシンの5433ポートからコンテナ内のpostgresサーバ(5432ポートで待機)にアクセスすることにする.

また,ユーザ情報等の環境変数の内容は外部のファイル".env"に分離したので,長くならずすっきりしたと思う.

また,イメージの初回起動時に実行される.sqlファイルを格納したフォルダはdocker-entrypoint-initdb.dにマウントされるように設定した.

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% cat docker-compose.yml
version: '3'

services:
  db:
    container_name: test-postgres-db
    build: ./postgres/
    env_file: ./postgres/.env
    volumes:
      - ./postgres/db:/docker-entrypoint-initdb.d
    ports:
      - "5433:5432"

Dockerfile

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% cat postgres/Dockerfile
FROM postgres:14-alpine
ENV LANG ja_JP.utf8

.env (ユーザ情報を定義した環境変数をまとめたファイル)

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% cat postgres/.env
POSTGRES_USER=postgres
POSTGRES_PASSWORD=hogehogepw
POSTGRES_DB=test_db

init.sql

コンテナ起動時に,データベースにテーブルが作成するようにする.以下の仮のテーブル(名前を保存する"members")を作ってみる.

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% cat postgres/db/intidb.sql
CREATE TABLE members (
  id integer,
  name varchar(30)
);
INSERT INTO members VALUES (1, 'John Smith');
INSERT INTO members VALUES (2, 'Richard Roe');

membersと名付けたテーブルに適当なエントリを挿入した.

イメージのビルド

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% docker-compose build

コンテナの起動

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% docker-compose up -d
Creating network "docker-postgres_default" with the default driver
Creating test-postgres-db ... done

クライアント(psql)でデータベースにアクセスしてみる

postgresサーバに接続

% psql -h localhost -p 5433 -U postgres
Password for user postgres:
psql (14.1)
Type "help" for help.

イメージが初回起動したときに作成されたデータベース(test_db)に接続し,membersテーブルからデータを取得する.

postgres=# \c test_db
You are now connected to database "test_db" as user "postgres".
test_db=# \dt
          List of relations
 Schema |  Name   | Type  |  Owner
--------+---------+-------+----------
 public | members | table | postgres
(1 row)


test_db=# select * from members;
 id |    name
----+-------------
  1 | John Smith
  2 | Richard Roe
(2 rows)


test_db=# \q

最後に (後片付け)

期待通りに動いたのを確認した後は以下のようにコンテナを終了してボリュームも一緒に削除した.

[~/Code/Project/proj_y2021/proj1229/docker-postgres]
% docker-compose down --rmi all --volumes

Docker(docker-compose)でMySQLの環境構築をする覚書

はじめに

最近Dockerの使い方を勉強している.MySQLの導入のために環境構築の仕方を試してみた.

特に参考にしたのが以下

やったこと

基本的なことだが,mysqlのサーバをDockerで立ち上げて,作成したサンプルのデータベースにmysqlクライアントでアクセスするまでを行った.

やったことの手順

  • (1) まずベースとなるMySQLのイメージを以下のようにpullした
docker pull mysql:8.0
  • (2) 最終的にイメージをビルドするのに必要なファイル(docker-compose.yml , etc.)を作成

  • (3) イメージをビルド,コンテナを起動

  • (4) mysqlクライアントでアクセスし,データベースを操作して遊んでみる

フォルダ構成

環境構築は以下の構成で進める.

[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql]
% tree .
.
├── docker-compose.yml
└── mysql
    ├── Dockerfile
    ├── db
    │   ├── 01_init_db.sql
    │   └── 02_insert_data.sql
    └── my.cnf


2 directories, 5 files

コンテナ起動時に実行される".sql"ファイルについて

本実験では簡単なデータベースを作成してmysqlクライアントでアクセスするまでをやりたかった.

コンテナ内のdocker-entrypoint-initdb.dに配置されて実行される.sqlファイルは参考サイトで取り上げられていた01_init_db.sql02_insert_data.sqlが良さげだったので,今回はこれらを使うことにした.

作成した各ファイルについて

docker-compose.yml

[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql]
% cat docker-compose.yml
version: "3"


services:
    mysqldb:
        container_name: hello-mysql-db
        build: ./mysql
        volumes:
            - ./mysql/db:/docker-entrypoint-initdb.d/
        ports:
            - "3307:3306"

mysql/dbにはコンテナ起動時に実行したい.sqlファイル群が置かれているが,これらはdocker-entrypoint-initdb.dにマウントされ,コンテナ起動時に実行されることとなる.

コンテナを起動後にローカルPCの3307ポートからアクセスする予定だったので,上記の"ports:"の設定にした.

Dockerfile

[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql]
% cat mysql/Dockerfile
FROM mysql:8.0

ENV MYSQL_ROOT_PASSWORD=secretpw

ADD ./my.cnf /etc/mysql/conf.d/my.cnf

my.cnf

[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql]
% cat mysql/my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci


[client]
default-character-set=utf8mb4

Dockerイメージのビルドとコンテナの起動

以下のようにしてビルド

[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql]
% docker-compose build
Building mysqldb
[+] Building 0.1s (7/7) FINISHED
 => [internal] load build definition from Dockerfile                                           0.0s
 => => transferring dockerfile: 36B                                                            0.0s
 => [internal] load .dockerignore                                                              0.0s
 => => transferring context: 2B                                                                0.0s
 => [internal] load metadata for docker.io/library/mysql:8.0                                   0.0s
 => [internal] load build context                                                              0.0s
 => => transferring context: 27B                                                               0.0s
 => [1/2] FROM docker.io/library/mysql:8.0                                                     0.0s
 => CACHED [2/2] ADD ./my.cnf /etc/mysql/conf.d/my.cnf                                         0.0s
 => exporting to image                                                                         0.0s
 => => exporting layers                                                                        0.0s
 => => writing image sha256:c5da021b5c42d0ef0edf95c1a4c162675ede9c927420f3a6ced8694860fd72d4   0.0s
 => => naming to docker.io/library/docker-mysql_mysqldb                                        0.0s


Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

コンテナの起動

[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql]
% docker-compose up -d
Creating network "docker-mysql_default" with the default driver
Creating hello-mysql-db ... done

MySQLサーバにクライアントでアクセスしてデータベースで遊んでみる

ローカルのポート(今回は3307)からアクセスする.

[~]
% mysql -h 127.0.0.1 -P 3307 -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.27 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

コンテナ起動時に作成されたデータベースへのアクセス

サンプルとして作成したデータベースからデータが読めるのを確認.

mysql> use jpaddress
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A


Database changed
mysql> SELECT * FROM city WHERE Zip LIKE '108007%';
+-------+---------+---------------+---------+--------------------------------------+------------+-----------+-------------------------------+
| ID    | Zip     | JP-Prefecture | JP-City | JP-Address                           | Prefecture | City      | Address                       |
+-------+---------+---------------+---------+--------------------------------------+------------+-----------+-------------------------------+
| 37960 | 1080075 | 東京都        | 港区    | 港南 (次のビルを除く)             | Tokyo To   | Minato Ku | Konan (次のビルを除く)        |
| 38064 | 1080072 | 東京都        | 港区    | 白金                                 | Tokyo To   | Minato Ku | Shirokane                     |
| 38065 | 1080071 | 東京都        | 港区    | 白金台                               | Tokyo To   | Minato Ku | Shirokanedai                  |
| 38068 | 1080074 | 東京都        | 港区    | 高輪                                 | Tokyo To   | Minato Ku | Takanawa                      |
| 38389 | 1080073 | 東京都        | 港区    | 三田 (次のビルを除く)             | Tokyo To   | Minato Ku | Mita (次のビルを除く)         |
+-------+---------+---------------+---------+--------------------------------------+------------+-----------+-------------------------------+
5 rows in set (0.09 sec)


mysql>

試しにデータベースを作ってみる

データベースsample_dbを作成.

mysql> CREATE DATABASE sample_db;
Query OK, 1 row affected (0.01 sec)

新規作成したデータベースを使用(use)する.

mysql> use sample_db
Database changed

テーブルの作成とデータの追加

mysql> CREATE TABLE test_tbl (
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> name TEXT NOT NULL);
Query OK, 0 rows affected (0.02 sec)


mysql> INSERT INTO test_tbl (name) VALUES ("HIRO"),("Steve"),("michel");
Query OK, 3 rows affected (0.02 sec)
Records: 3  Duplicates: 0  Warnings: 0

作成したテーブルからデータを読めるのを確認した.

mysql> select * from test_tbl;
+----+--------+
| id | name   |
+----+--------+
|  1 | HIRO   |
|  2 | Steve  |
|  3 | michel |
+----+--------+
3 rows in set (0.01 sec)


mysql>

(最後に) 後片付け

今回は環境構築の仕方を試しに色々実験しただけだったので,終わった後は後片付けとして作成されたコンテナやボリュームを削除して終わりとする.

以下を実行し,docker-composeで起動したコンテナは終了され,ボリュームも削除されたことを確認した.

docker-compose down --rmi all --volumes

今回はここまでにする.SQLもうちょっと勉強したほうがいいかも.

RustでWebAssemblyに軽く入門した

はじめに

これはあくまで個人的な日々の記録であり備忘録です.WebAssemblyに興味があり,Rustで使えるとのことなので,使ってみました.メモを書き残しておきます.

以下の参考サイトを習っています.

やったのは,"alert"で警告ダイアログのポップアップを出すだけでJavaScriptでも書ける内容ですが,今回はそれをRustで書いたコードから生成されたwasmファイルに処理を書いていて,それをjavascriptから呼び出す感じの処理を書いています.

この記事はあくまで備忘録ですが,以下の手順でメモしています.

  1. 環境構築
  2. rustでwasmバイナリにコンパイルしたいコードを書く
  3. wasmバイナリにコンパイル
  4. webサーバを立ち上げてブラウザでアクセスしてwasmで書いた処理を実行させてみる

環境構築

まず,使ったRustとcargoのバージョンは以下です.共に1.55.0です.以下のようなコマンドで確認できます.

[~]
% rustc -V
rustc 1.55.0 (c8dfcfe04 2021-09-06)
[~]
% cargo --version
cargo 1.55.0 (32da73ab1 2021-08-23)
[~]
%

Rustで書いたコードをwasmにコンパイルする,wasm-packが必要らしいです.以下のようにcargo installで入れます.

cargo install wasm-pack

使ってみる

cargo newで"--lib"オプションをつけて,ライブラリ用のターゲットを作ります.cargo newに関するオプションの詳細は公式docから確認可能

名前はhi-wasmにしました(helloじゃなくてhiにしただけで特に意味はないです).

cargo new --lib hi-wasm

wasmにコンパイルされるコードをRustで書く

src/lib.rsにRustでWebAssemblyで実行するコードを書いてみる.

[~/Code/Project/Project_WebAssembly/hi-wasm]
% cat src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hi, {}!", name));
}

wasm-bindgenというのが気になったのだけれど,これは"RustとJavascriptの間でやり取りする"ために必要であり,JavascriptからRustで書いたAPI(上記コードでいうgreet)を呼び出せるそうです.

このときのCargo.toml

Cargo.tomlは以下のようにする.

[~/Code/Project/Project_WebAssembly/hi-wasm]
% cat Cargo.toml
[package]
name = "hi-wasm"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

Rustコードをwasmへコンパイルする

wasm-pack buildコマンドで,上記のRustのコード(src/lib.rs)をwasmにコンパイルする.参考サイトにならい,"--target web"オプションをつけた.

[~/Code/Project/Project_WebAssembly/hi-wasm]
% wasm-pack build --target web

これを機にwasm-packについて調べた

wasm-packというのを初めて使ったし,"--target web"という書き方がよく分からなかったので調べてみた.公式docを読んで知ったのが以下.

オプション 使用法(usge) 説明(description)
web Native in browser(ブラウザネイティブ?) Outputs JS that can be natively imported as an ES module in a browser, but the WebAssembly must be manually instantiated and loaded. (ESモジュールとしてブラウザにネイティブにインポートできるJSファイルを出力する.ただし,WebAssemblyはマニュアルにインスタンス化されロードされなければならない)

webオプションは,ブラウザにロードされるJSファイルを出力するという意味らしいです.

ちなみに,wasm-pack buildは以下のようなコマンドらしいです.

The wasm-pack build command creates the files neccessary for JavaScript interoperability and for publishing a package to npm. This involves compiling your code to wasm and generating a pkg folder. This pkg folder will contain the wasm binary, a JS wrapper file, your README, and a package.json file.

「"wasm-pack build"は,Javascriptとの相互運用性に必要なファイル群を作成するためのコマンドであり,また,packageをnpmで配布するためのコマンドです.これはあなたのコードをwasmへとコンパイルし,pkgフォルダを生成します.pkgフォルダはwasmバイナリ,JSラッパーファイル,そしてREADMEとpackage.jsonファイルを含んでいます.」

wasmで書いた処理をjavascriptから呼び出すwebページにアクセスしてみる

参考サイトに習い,index.htmlを作ります.pkg以下にあるhi_wasm.jsをインポートしてます.wasmで書いた処理を呼び出すjsコードみたいに書けそうですね

[~/Code/Project/Project_WebAssembly/hi-wasm]
% cat index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>hi-wasm example</title>
  </head>
  <body>
    <script type="module">
      import init, {greet} from "./pkg/hi_wasm.js";
      init()
        .then(() => {
          greet("WebAssembly")
        });
      </script>
  </body>
</html>

webサーバを立ててアクセスする

どこかで見かけた知恵ですが,pythonでwebサーバを立てると,localhost:8000からアクセスできます.

アラートボックス( "Hi WebAssembly"が出たら,ここでは成功です.(普通にjavascriptでも書けそうな処理ですが)

[~/Code/Project/Project_WebAssembly/hi-wasm]
% python3 -m http.server

f:id:aster-wildcard:20211031181725p:plain
こんな感じにアラートボックスが出た

感想と今後の展望

  • npmは慣れてなかったんだけど,python3でwebサーバを立ててもカジュアルにアクセスできるってことが分かったので,敷居が下がった.

  • チュートリアルが分かりやすくて学習に助りましたありがとう.こんな感じか,って分かった.使いこなせるようになりたい.本腰入れて公式のドキュメントとかからじっくり読もう

  • もっと勉強して使いこなせるようになりたいと思った.

参考文献

本記事はこれを実践してました.

wasm-packのコマンドについて気になったので調べるのに見ていました.