Google Compute Engine 試用メモ - Web Application ▲factory top


出社人数掲示板 2020 May 28

昨今のコロナ禍により、 会社フロアーへの出社人数の制約や把握が必要になる場合があると思います。 そのような時、みんなで共有して使える「出社人数掲示板」を、 Google Compute Engine の試用を兼ねて試作してみました。 ただし、現状では実際の運用が出来るレベルではありません。 本件はその試作メモです。

スマホで見た場合の出社人数掲示板の表示例
出社を予定している日にちをクリックすると「出社」の+1、 あるいは「取消」の-1が出来ます

 

実際の 出社人数掲示板はこちら です。実運用していないので自由に触って大丈夫です。 アメリカのオレゴン州にあるGoogle無料サーバーのため、 アクセスに少し時間がかかります。

 

おさらい 2020 May 18-20

Google Cloud Shell とは Google Compute Engine とは

 

無料のサーバインスタンスを立てる 2020 May 21

VM を立て OS を入れる
  • マシン名 : factorylabusinstance とする (factory lab us instance)
  • 無料枠の条件 : f1-micro, us-west1, ディスク容量 30GB、を選択
  • OS : Ubuntu 16.04 LTS を選択
  • host-name : factorylabus.c.siteyamakatsu-######.internal
  • 新しい静的IPアドレス: factorylabus 35.199.148.152 を取る
    ★静的IPアドレスはVMがないと課金対象になるので注意、削除方法は~
  • ディスクデバイス名 : factorylabusdisk とする
  • 接続は:VMインスタンスの SSH ボタンから「ブラウザウインドウで開く」
  • 終了は:exit
  • ファイル転送は:ブラウザターミナルのメニュー使用
Google Cloud Shell で作業するならここまでで良いが、 ローカルから term 等でログインするには SSH 設定が必要。

ubuntu に Web Server apache2 を立てる
  • $ sudo apt install apache2
  • (再)起動は $ sudo systemctl restart apache2
  • エラー確認は $ journalctl -xe
config
/etc/apache2/apache2.conf
ServerName factorylabus

#
# This should be changed to whatever you set DocumentRoot to.
#
<Directory /home/USER-NAME/mnt>
	Options Indexes FollowSymLinks
	AllowOverride None
	Require all granted
	Order allow,deny
	Allow from all
</Directory>

#CGI 設定
LoadModule alias_module /usr/lib/apache2/modules/mod_alias.so
LoadModule cgi_module /usr/lib/apache2/modules/mod_cgi.so
ScriptAlias /cgi-bin/ /home/USER-NAME/cgi-bin/
<Directory home/USER-NAME/cgi-bin>
    Options FollowSymLinks ExecCGI
    AllowOverride None
    Require all granted
    Order allow,deny
    Allow from all
    AddHandler cgi-script .cgi .sh .class .pl
</Directory>

/etc/apache2/sites-available/000-default.conf
DocumentRoot /home/USER-NAME/mnt

 

Cloud Storage をVMにマウントする 2020 May 22-23

デフォルトではCloud Storageは、VMからは見えませんが、 VMのファイルシステムとしてマウントするコマンド Cloud Storage FUSE があります。 「Cloud Storage バケットへの接続」 、及び 「Cloud Storage FUSE」 を参照。

gcsfuseのインストールはgithubのdocs/installing.mdの 1と2(3は不要)をコピペして実行します。 しかし、任意のディレクトリに対してはうまくマウントできず、 取り合えずホーム直下に作りました。 また、マウントはエンジン起動のたびに行う必要があります。 ちなみに、本エンジンは常時稼働しています。
  • $ mkdir ~/mnt
  • $ gcsfuse --implicit-dirs factorylabus ~/mnt
    gcsfuse --implicit-dirs としないと階層ディレクトリが見えない
    付けても ls で [.][..] が見えない
  • unmount は $ fusermount -u ~/mnt

デフォルトでは Engine から Storage へは読み出しのみの設定となっているので、 書き込むには Engine を止め、VMインスタンスの詳細画面から、 編集→アクセススコープを「各APIに~」に変更し、ストレージを「フル」とする。

さて、 ここでapacheからCloud Storageへのアクセス権がなくて見えないという問題が発生。 「GKEのコンテナ内の~」 によれば、apache のユーザー名で gcsfuse しないと apache に見えないそう。
  • /etc/apache2/envvars より Apache user 名は、
  • APACHE_RUN_USER=www-data だから、
  • $ sudo -u www-data gcsfuse ... すれば、なるほど、、
しかし、それでは今度は私からファイルシステムが見えない。 そこで苦肉の策として、apache のユーザー名を私のユーザー名としたら、 apache からも、私からもストレージが見れるようになりました。 これではマルチユーザーでは使えませんが、 ストレージに癖があるので今回はこれで良しとします。

/etc/apache2/envvars
#export APACHE_RUN_USER=www-data
#export APACHE_RUN_GROUP=www-data
export APACHE_RUN_USER=USER-NAME
export APACHE_RUN_GROUP=USER-NAME

 

Web App を構築する 2020 May 24

その前に、私は Assembler, C, C++, java, javascript しか使った経験がなく、 今回の超簡単アプリもjavaで書きました。 その為、VMにjava実行環境をインストールします。 headless の方を入れます。
  • $ sudo apt install openjdk-8-jre-headless
  • ちなみに uninstall は以下
    $ sudo apt --purge remove openjdk-8-jre-headless
    $ sudo apt-get autoremove
出社人数カウントコード count.java
//
// Usage:
//    $ java count mode [param]
//      mode  0=31日分の情報を返す
//            1=param引数の日を+1し、情報を返す
//            2=param引数の日を-1し、情報を返す
//      param +-する日にち
//
import	java.io.FileInputStream;
import	java.io.InputStreamReader;
import	java.io.BufferedReader;
import	java.io.FileOutputStream;
import	java.io.DataOutputStream;
import	java.io.IOException;

public class count {
    static String FILENAME = "count.string";

    public static void main(String[] args) {
	int mode = 0;
	int param = 0;
        if ( args.length < 1 ) {
	    System.out.println("Usage: java count mode [param]");
	    return;
	}
	mode = Integer.parseInt(args[0]);
	if ( mode != 0 ) {
	    if ( args.length < 2 ) {
		System.out.println("Usage: java count mode [param]");
		return;
	    }
	    param = Integer.parseInt(args[1]);
	}
	String[] lines = new String[40];

	try {
	    FileInputStream fis = new FileInputStream(FILENAME);
	    InputStreamReader isr = new InputStreamReader(fis);
	    BufferedReader br = new BufferedReader(isr);
	    for( int i=0; i < 31 ; i++ ) {
		lines[i] = br.readLine();
	    }
	    br.close();
	} catch(IOException e) {}

	if ( mode == 1 ) {
	    lines[param] = "" + (Integer.parseInt(lines[param])+1);
	} else if ( mode == 2 ) {
	    int value = Integer.parseInt(lines[param]);
	    if ( value > 0 )
		lines[param] = "" + (value - 1);	    
	}
	if ( mode != 0 ) {
	    try {
		FileOutputStream fos = new FileOutputStream(FILENAME);
		DataOutputStream dos = new DataOutputStream(fos);
		for( int i=0; i < 31 ; i++ ) {
		    dos.writeBytes(lines[i] + "\n");
		}
		dos.close();
	    } catch(IOException e) {}
	}

	for( int i=0; i < 31 ; i++ ) {
	    System.out.println(lines[i]);	// std out
	}
    }
}

 

さて、20年程前に会社のApache Web ServerでSSIを使ったことがあり、 少し考えましたが、SSIはあくまで静的なプリプロセッサ―であり、 静的なWeb Siteには使えますが、 引数や変数を伴った動的なWeb Siteで活躍する仕組みではないようです。

apacheを使う場合は 「ApacheとWebアプリケーションの連携」 などという方法もあるようですが本筋ではない気がします。

ポイントは、サーバー側で出社人数を管理している、 上記count.javaのコマンドラインと、ユーザーのブラウザ側のHTMLコードを、 インターネット越しにどう接続するかです。

Web App のお勉強
  • HTTP における GET と POST
  • GET - http://hoge.jp/index.html?foo=1&boo=2 の引数の名前と値
  • POST - フォーム内容など長くかつ長さが不定なものとか
syussya/index.htm の XMLHttpRequest 関連部分
<script language="JavaScript">
  var ScriptUrl = "http://35.199.148.152/cgi-bin/count.pl";
  var mask;
  let xhr = new XMLHttpRequest();

  function codeGen() {
    mask = true;
    xhr.open("GET", ScriptUrl + "?mode=0", true);
    xhr.send();

    xhr.onerror = function() {
      alert("Request failed");
    };

    xhr.onload = function() {
      if ( xhr.status != 200 ) {
        alert(`Error ${xhr.status}: ${xhr.statusText}`);
      } else {
        update();
      }
    };

    // --
    var date = new Date();
    var month = date.getMonth()+1;
    var today = date.getDate();
    var lastDay = new Date(date.getFullYear(), month, 0).getDate();

    var i = 0;
    document.write("<table cellpadding=4>");
    while( i < 7 ) {
      document.write("<tr><td valign=top>");
      document.write("  <div class='item0'>");
      document.write("    <font class='mon_day'>" + month + "/" +  today + "</font>");
      document.write("  </div>");
      document.write("</td><td>");
      document.write("  <a class='toggle btnNum' name='num'> - </a>");
      document.write("  <div class='item1'>");
      document.write("   <p><a href='' class='btnItem' onClick='return plus("+ today + ")'>出社します</a></p>");
      document.write("   <p><a href='' class='btnItem' onClick='return mimus("+ today + ")'>取消します</a></p>");
      document.write("  </div>");
      document.write("</td></tr>");
      if ( lastDay == today ) {
        month++;
        today = 0;
      }
      today++;
      i++;
    }
    document.write("</table>");
  }

  function update() {
    mask = false;
    var counts_string = xhr.response; // 文字列で返った
    var syussya = counts_string.split("\n");

    var date = new Date();
    var month = date.getMonth()+1;
    var today = date.getDate();
    var lastDay = new Date(date.getFullYear(), month, 0).getDate();

    var i = 0;
    while( i < 7 ) {
      document.getElementsByName("num")[i].innerHTML = syussya[today-1];
      if ( lastDay == today ) {
        today = 0;
      }
      today++;
      i++;
    }
  }

  function inc_dec(day, inc_dec) {
    mask = true;
    xhr.open("GET", ScriptUrl+"?mode="+(inc_dec? '1':'2')+"&param="+(day-1), true);
    xhr.send();
  }

  function plus(day) {
    if ( mask == false )
      inc_dec(day, true);
    return false;
  }

  function mimus(day) {
    if ( mask == false )
      inc_dec(day, false);
    return false;
  }
</script>

 

両者をつなぐ引数受け渡し部分CGI 「Webサーバーを作る(アプリを動かす)」の第9回 を参照。今回はperlを使い、引数をjavaアプリに渡しました。
  • VMへのperlのインストール $ sudo apt-get install libcgi-session-perl
  • perl CGIスクリプトには実行権を与えておく必要がある
  • perl の改行コード指定 #!/usr/bin/perl -w
count.pl
#!/usr/bin/perl -w

use strict;
use warnings;
use CGI;

print "Content-type: text/html\n\n";

# GET パラメータを取得
my $q = new CGI;
 
# パラメータ名を指定して取り出す
my $mode = $q->param('mode');  
my $param = $q->param('param'); 

# コマンドラインを形成しjavaアプリを呼び出す
my $command = 'java count ' . $mode . ' ' . $param;
system($command);

 

課題

サーバー側 Web Api と、クライアント側 HTML の URL が異なる場合の オリジン間リソース共有 (CORS) を行うには?

その後の Tips


無料VMの変更 2021 Aug 15

2021年7月21日、Google から、無料のVMエンジンが、 F1-micro から E2-micro に変更されるという連絡があり、 その対応を行った。
  • GCPダッシュボードより Compute Engine → factorylabusinstance 選択
    エンジンを停止させ、編集より
    「マシンの構成」で e2-micro を選択
    保存し、エンジン再起動
  • Compute Engine に戻り SSH ターミナル起動
  • apache2 再起動 ~$ sudo systemctl restart apache2
  • Cloud Storage FUSE 再起動 ~$ gcsfuse --implicit-dirs factorylabus ~/mnt