2020/08,江端さんの技術メモ

Dockerコンテナのサーバ化には、目処がついたので、今度はクライアント化の方を検討中です。

まずは、mkdir ~/go_echo/client を作って、そこにclient.goを放り込みました。

現時点での、Dockerfileは以下です。

# ベースとなるDockerイメージ指定
FROM golang:latest
# コンテナ内に作業ディレクトリを作成
RUN mkdir /go/src/client
# コンテナログイン時のディレクトリ指定
WORKDIR /go/src/client
# ホストのファイルをコンテナの作業ディレクトリに移行
ADD . /go/src/client

# gorillaのパッケージを入れる
RUN go get github.com/gorilla/websocket

現時点での、docker-compose.ymlは以下です。

version: '3' # composeファイルのバーション指定
services:
  client: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化

    volumes:
      - .:/go/src/client # マウントディレクトリ指定
    #ports:
    #   - "8080:8080"
    #expose:
    #   - "8080"

で、$ docker-compose build
$ docker-compose up -d
$ (winpty) docker container exec -it client_client_1 bash を実施して、

root@d561a99d0f67:/go/src/client# go run client.go

を実施したら、

connecting to ws://localhost:8080/echo
dial:dial tcp 127.0.0.1:8080: connect: connection refused
exit status 1

となりました。このlocalhostは、コンテナ内のホストだから、コンテナの外にあるホストOSや、別のコンテナの中にあるサーバには、繋らないんだろうなぁ、と。

その証拠に、

root@d561a99d0f67:/go/src/client# ping kobore.net
PING kobore.net (49.212.198.156) 56(84) bytes of data.
64 bytes from 49.212.198.156 (49.212.198.156): icmp_seq=1 ttl=37 time=12.0 ms

と、外部には、バッチリ繋っている(でも、これは、ちょっと凄いと思う)


では、ここからトライアル。

docker-compose.ymlの最後に、

extra_hosts:
- "local_dev:192.168.0.1"

を追加 → 失敗。 ちなみに、"127.0.0.1"も失敗しました。

もしからしたら、と思い、 extra_hosts:をコメントアウトして、client.goの方のアドレスを直書きしてみました。今、PC本体のアドレスが、

C:\Users\ebata>ipconfig

Windows IP 構成

イーサネット アダプター イーサネット:

接続固有の DNS サフィックス . . . . .:
IPv4 アドレス . . . . . . . . . . . .: 192.168.0.8
サブネット マスク . . . . . . . . . .: 255.255.255.0
デフォルト ゲートウェイ . . . . . . .: 192.168.0.1

なので、client.goの一行を以下のように換えてみました。

//var addr = flag.String("addr", "localhost:8080", "http service address")
//var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
var addr = flag.String("addr", "192.168.0.8:8080", "http service address")

これで動きましが、正直、なんとも気持ち悪いです。

ただ、これを解決するためには、dockerでIPフォワーディングとかやらなければならないのだと思うと、結構ウンザリした気分になりましたので、このままで放置することにします(動けばいいんですよ。ここは踏んばるところではありません)

以上

2020/08,江端さんの技術メモ

前提の記事はこちら

このサーバをさらにDockerのコンテナに搭載します。

サーバも何も入っていないが、Golang開発環境だけが入っているコンテナを作ります。DockerでGoの開発環境を構築するを参考させて頂きました。

mkdir work # workディレクトリ作成
cd work # 作成したworkディレクトリに移動

ここに、Dockerfileとdocker-compose.ymlを作ります。

(Dockerfileの内容)
# ベースとなるDockerイメージ指定
FROM golang:latest
# コンテナ内に作業ディレクトリを作成
RUN mkdir /go/src/work
# コンテナログイン時のディレクトリ指定
WORKDIR /go/src/work
# ホストのファイルをコンテナの作業ディレクトリに移行
ADD . /go/src/work

上記の、/go/src/work は、ローカル(PC)のディレクトリではなくて、コンテナの中のディレクトリなので、パスの構成は気にしなくてよい。

(docker-compose.yml
の中身)
version: '3' # composeファイルのバーション指定
services:
  app: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化
    volumes:
      - .:/go/src/work # マウントディレクトリ指定

次に、

docker-compose build

を実行する。

$ docker-compose build
Building app
Step 1/4 : FROM golang:latest
 ---> a794da9351a3
Step 2/4 : RUN mkdir /go/src/work
 ---> Using cache
 ---> 094b928e9bfb
Step 3/4 : WORKDIR /go/src/work
 ---> Using cache
 ---> b7a938d02446
Step 4/4 : ADD . /go/src/work
 ---> 9d95f42d64e3
Successfully built 9d95f42d64e3
Successfully tagged work_app:latest

さらに、docker-compose up -d を実行する。

$ docker-compose up -d
Creating network "work_default" with the default driver
Creating work_app_1 ... done

コンテナができているか同化を確認します。

$ docker-compose ps
   Name      Command   State   Ports
------------------------------------
work_app_1   bash      Up

現在、~/go_echo にある、server.goを、~/go_echo/workにコピーします。

これで、server.go が、ローカル(PC)と、コンテナの中で共有されるようになります。

では、コンテナの中に入ります。

$ winpty docker container exec -it work_app_1 bash # 江端のMSYS2 のシェルでは、"winpty"を付ける必要があるが、通常のシェルでは不要

root@abe44622eccf:/go/src/work# ls
'#main.go#'   Dockerfile   Dockerfile~   server.go   docker-compose.yml   docker-compose.yml~   main   main.go   main.go~   websocket-server.go

ここで、Goプログラムを実行します。

root@abe44622eccf:/go/src/work# go run server.go
server.go:21:2: cannot find package "github.com/gorilla/websocket" in any of:
        /usr/local/go/src/github.com/gorilla/websocket (from $GOROOT)
        /go/src/github.com/gorilla/websocket (from $GOPATH)

ふむ、やはり、こうなりますね。では、パッケージをインストールしましょう。

root@abe44622eccf:/go/src/work#  go get github.com/gorilla/websocket

何のメッセージも出さずに20秒後くらいに静かにプロンプトが戻ってきました(ちょっと不安になる)。

root@abe44622eccf:/go/src/work# go run client_multi_agent.go
connecting to ws://localhost:8080/echo
dial:dial tcp 127.0.0.1:8080: connect: connection refused
exit status 1

うーん、やっぱりネットワーク回りの設定をしていないから当然か。ポート開けていないし。docker-compose downして、docker-compose.ymlに以下を追加してみました。

ports:
  - "8080:8080"
$ more docker-compose.yml
version: '3' # composeファイルのバーション指定
services:
  app: # service名
    build: . # ビルドに使用するDockerfileがあるディレクトリ指定
    tty: true # コンテナの起動永続化
    volumes:
      - .:/go/src/work # マウントディレクトリ指定
    ports:
      - "8080:8080"

それと、"go get github.com/gorilla/websocket"を毎回実行しない為には、Dockerfileに "RUN go get github.com/gorilla/websocket"を1行加えれば良いみたい。

# ベースとなるDockerイメージ指定
FROM golang:latest
# コンテナ内に作業ディレクトリを作成
RUN mkdir /go/src/work
# コンテナログイン時のディレクトリ指定
WORKDIR /go/src/work
# ホストのファイルをコンテナの作業ディレクトリに移行
ADD . /go/src/work

# gorillaのパッケージを入れる
RUN go get github.com/gorilla/websocket

そんでもって、再びdocker-compose build → docker-compose up -d を実施して、docker container exec -it work_app_1 bash をして、コンテナの中に入って、go run server.goをやったんだけど、ブラウザで、http://localhost:8080やっても表示されない。

C:\Users\ebata\go_echo\work>curl http://localhost:8080/
curl: (52) Empty reply from server

となっているところを見ると。8080ポートは外側に見えていないみたい。

でも、

C:\Users\ebata\go_echo\work>netstat -a | grep 8080
  TCP         0.0.0.0:8080           DESKTOP-P6KREM0:0      LISTENING
  TCP         [::]:8080              DESKTOP-P6KREM0:0      LISTENING

にはなっているんだよなぁ。

試しに、コンテナの中で"curl http://localhost:8080/"をやってみたら、

oot@b30e685f9cc6:/go/src/work# curl http://localhost:8080/

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
    var output = document.getElementById("output");
    var input = document.getElementById("input");
    var ws;

てな感じで、ばっちり見えます。

うーん、変だなぁ。"8080:8080"ってポートフォワードしているじゃないのか? WebSocketの場合は、特殊な設定がいるのか?分かりません。

もう6時間くらい闘ったので、引き上げようとして、最後に、"curl: (52) Empty reply from server" でググってみたら、ドンピシャな感じの記事を見つけました。

docker上のアプリにlocalhostでアクセスしたらERR_EMPTY_RESPONSEが出る

まさに、同じ現象が表われていて、『そう! そう!』と言いながら読み進めていき、結果として、

(解決) アプリの設定を0.0.0.0でLISTENするよう変更する

と記載されていましたので、server.goのソースコードを、

//var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト

と変更してみたところ、http://localhost:8080 でWebもcurlも表示されるようになりました。

ああ、これで、サーバのコンテナ化にメドがついた ―― と思ったら、どっと疲れが出てきました。

『Dockerのコンテナは、江端が作れ』と、指示されていましたので。


Dockerfileの最後に、

CMD ["go", "run", "server.go"]

をつけると、dockerコンテナをサーバ化にできる。

ただし、

# docker-compose build, 
# docker-compose up (-d ← これを付けたらダメ。バックグラウンドで起動するとDockerが終了してしまう)

で起動すること。

2020/08,江端さんの忘備録,江端さんの技術メモ

Go言語は、スレッド化による並列処理が得意という話を聞いていましたので、先日の

の"client.go"を改造して、「とりあえず、動かして、止めることができればいい」という割り切りだけでコードをごそごそを変えてみました。(server.goは変更なしで大丈夫)。 ファイルは、~/go_echo/に置くこととします。

// go run client_multi_agent.go

// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

package main

import (
	"flag"
	"log"
	"net/url"
	"os"
	"os/signal"
	"time"

	"github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")


var interrupt = make(chan os.Signal, 1) // Go のシグナル通知は、チャネルに os.Signal 値を送信することで行います。
			  	 		   			  	 // これらの通知を受信するためのチャネル (と、プログラムが終了できること
										 // を通知するためのチャネル) を作ります。
var	u = url.URL{Scheme: "ws", Host: *addr, Path: "/echo"} // JSON型の配列記述方法?

func main() {
	flag.Parse()     //引数の読み込み argv, argcと同じ
	log.SetFlags(0)  // ログの出力で時間の情報、この時点で0秒にセット

//	interrupt := make(chan os.Signal, 1) // Go のシグナル通知は、チャネルに os.Signal 値を送信することで行います。
//			  	 		   			  	 // これらの通知を受信するためのチャネル (と、プログラムが終了できること
//										 // を通知するためのチャネル) を作ります。

	signal.Notify(interrupt, os.Interrupt) // 指定されたシグナル通知を受信するために、 与えられたチャネルを登録
							 			   // します。

	log.Printf("connecting to %s", u.String()) // ここは単にプリントしているだけ(だろう)

	///// ここまでは共通 /////

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		

	go sub_main()
	time.Sleep(time.Millisecond * 333)  // 333ミリ秒		
	
	ticker := time.NewTicker(time.Second)  // 1秒おきに通知 (sleepと同じ)		

	for {
		select{
    	   case <-ticker.C: // tickerのチャネルはデフォルトで付いているらしい
		   case <-interrupt:  // こっちは手動割り込みだな検知だな
		        return;
		}
     }
}

func sub_main(){
	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) // これがコネクションの実施
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close() // deferは、どこに書かれていようとも、関数から抜ける前に実行される

	done := make(chan struct{}) // 配列といってもいいし、並行処理用のキューといってもいい
                                // 値が入っていないとデッドロックする

	go func() {  // 受信用スレッドを立ち上げる(スレッドの中でスレッド立ち上げているが、大丈夫だろうか)
		defer close(done)
		for {
			_, message, err := c.ReadMessage() // このメソッドの返り値は3つで、最初の返り値は不要
			if err != nil {
				log.Println("read:", err)
				return
			}
			log.Printf("recv: %s", message)  // 受信したら、そのメッセージを表示する
		}
	}()

	ticker := time.NewTicker(time.Second)  // 1秒おきに通知 (sleepと同じ)
	defer ticker.Stop() // このループを抜ける時に終了する

	for {            // 無限ループの宣言かな(C/C++ で言うとろの、whileとかdoとか)
		select {
		case <-done: // doneの中に何かが入っていたら、このルーチンはリターンして終了となる
			 		 // (でも何も入っていないところを見ると、func()ルーチンの消滅で、こっちが起動するんだろう)
			return
		case t := <-ticker.C: // tickerのチャネルはデフォルトで付いているらしい
			   	  			  // 時間が入ってくるまでロックされる	 
			   	  			  // この場合1秒単位でチャネルに時間が放り込まれるのでそこで動き出す。

			err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) // サーバに(時刻の)メッセージを送付する
			if err != nil {
				log.Println("write:", err)
				return
			}
		case <-interrupt:  // こっちは手動割り込みだな検知だな
			log.Println("interrupt")

			// Cleanly close the connection by sending a close message and then
			// waiting (with timeout) for the server to close the connection.

			// close メッセージを送信してから、サーバーが接続を閉じるのを
			// (タイムアウトして)待つことで、接続をきれいに閉じます

			err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
			if err != nil {
				log.Println("write close:", err)
				return
			}
			select {
				case <-done: // これも上記の"done"の説明と同じでいいかな
				case <-time.After(time.Second): // このメソッド凄い time.Secondより未来ならひっかかる
			}
			return
		}
	}
}

まあ、main()と sub_main()で同じ割り込みを使っているので、タイミングによっては、main()を止めることができず、sub_main()を個別に止めていくことになるが、とりあえず動いたので、これで良しとする。

go sub_main() と書くだけで、いきなりスレッド化させることができる手軽さは、かなり驚いた。

こんなんやこんなんで、いろいろ苦労してきたんだけど、これからも、マルチエージェントシミュレーションを自力で書いていくことを考えると、正式にGo言語に移るべきかな、と思っています。

go webassembly experiments をローカルPCで動かす とかも、いろいろやっていきたいことですし。

2020/08,江端さんの技術メモ

いらんことしなければいいのですが、function.phpとかをいじって、何度かWPを壊しかけました。

という訳で、バックアップツールの"All-in-One WP Migration"を入れました。

後は、"ファイル"を選んで、PCに保存できます。

以上

2020/08,江端さんの技術メモ

を見て、コーディングしてみたのですが、全然思うような結果になりません。

サンプル数を増やすと、どんどん0に近づいていくんだけど(そりゃそうだ)、サンプル数を減らした方が、2越えは発生するけど、解説の内容と会いません。

以下のコーディングで致命的に変な点があったら、どなたかご教示下さい。

package main

import (
	crand "crypto/rand"
	"fmt"
	"github.com/seehuhn/mt19937"
	"math"
	"math/big"
	"math/rand"
	"time"
)

type Scratch_Card struct {
	 UpperLeft  int64
	 LowerLeft  int64
	 UpperRight int64
	 LowerRight int64
}

var sc Scratch_Card

func main() {
	//計算開始日時
	started := time.Now()

	//乱数の設定
	seed, _ := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
	rng := rand.New(mt19937.New())
	rng.Seed(seed.Int64())


	for k := 0; k < 1; k++ {

		var pattern1_count, pattern2_count, pattern3_count, pattern4_count int64;
		var pattern1_sum, pattern2_sum, pattern3_sum, pattern4_sum 		   float64;
		var rightCat,rightPattern int64
		var leftCat,leftPattern int64




		//fmt.Println(sc.UpperLeft,sc.UpperRight)
		//fmt.Println(sc.LowerLeft,sc.LowerRight)
		//fmt.Println()


		for i := 0; i < 400; i++ {

			// スクラッチカード1枚を作る
			sc.UpperLeft  = rng.Int63()%2
			sc.LowerLeft  = rng.Int63()%2
			sc.UpperRight = rng.Int63()%2
 			sc.LowerRight = rng.Int63()%2


			// スクラッチカードの左側を削る
			if rng.Int63()%2 == 0 {   // 上を削ることになった
		  	   leftPattern = 0	      // 左上が削られたことのフラグ
		   	   leftCat = sc.UpperLeft // 左上のネコがスクラッチから表われる
			} else {                  // 下を削ることになった
		   	   leftPattern = 1	      // 左下が削られたことのフラグ
		   	   leftCat = sc.LowerLeft // 左下のネコがスクラッチから表われる
		    }

		    // スクラッチカードの右側を削る
		    if rng.Int63()%2 == 0 {    // 上を削ることになった
		       rightPattern = 0	       // 右上が削られることになったフラグ
		       rightCat = sc.UpperRight// 右下のネコがスクラッチから表われる
		    } else {                   // 下を削ることになった 
		       rightPattern = 1        // 右下が削られることになったフラグ
		       rightCat = sc.LowerRight// 右下のネコがスクラッチから表われる	   
		    }

		    ////////////////////

		    // ○○
		    // ××

		    if leftPattern == 0 && rightPattern == 0 {
		        pattern1_count += 1      // パターン1の回数の加算
		   
		       if leftCat == rightCat { // マークが同じ
		   	      pattern1_sum += 1
		       } else {                 // マークが違う 
		   	       pattern1_sum -= 1
		       }
		    }

		    // ××
		    // ○○
		    if leftPattern == 1 && rightPattern == 1 {
		       pattern2_count += 1   // パターン2の回数の加算   
		   
		       if leftCat == rightCat { // マークが同じ
		   	      pattern2_sum += 1
		       } else {                 // マークが違う 
		   	       pattern2_sum -= 1
		       }
		    }

			// ×○
			// ○×

		   if leftPattern == 1 && rightPattern == 0 {
		       pattern3_count += 1  // パターン3の回数の加算   
		   
		       if leftCat == rightCat { // マークが同じ
		   	      pattern3_sum += 1
		       } else {                 // マークが違う 
		   	       pattern3_sum -= 1
		       }
		    }

   		    // ○×
		    // ×○

		   if leftPattern == 0 && rightPattern == 1 {
		       pattern4_count += 1  // パターン4の回数の加算   
		   
		       if leftCat == rightCat { // マークが同じ
		   	      pattern4_sum += 1
		       } else {                 // マークが違う 
		   	       pattern4_sum -= 1   
		       }
		   }
	   }

	   fmt.Println("pattern1_count ", pattern1_count, "pattern1_sum", pattern1_sum)
	   fmt.Println("pattern2_count ", pattern2_count, "pattern2_sum", pattern2_sum)
	   fmt.Println("pattern3_count ", pattern3_count, "pattern3_sum", pattern3_sum)
	   fmt.Println("pattern4_count ", pattern4_count, "pattern4_sum", pattern4_sum)

	   var pattern1_ave,pattern2_ave,pattern3_ave,pattern4_ave float64

	   pattern1_ave = pattern1_sum / float64(pattern1_count)
	   pattern2_ave = pattern2_sum / float64(pattern2_count)
	   pattern3_ave = pattern3_sum / float64(pattern3_count)
	   pattern4_ave = pattern4_sum / float64(pattern4_count)
		
	   fmt.Println()

	   fmt.Println("pattern1_ave ", pattern1_ave)
	   fmt.Println("pattern2_ave ", pattern2_ave)
	   fmt.Println("pattern3_ave ", pattern3_ave)
	   fmt.Println("pattern4_ave ", pattern4_ave)

	   fmt.Println()
	  }

	//計算終了日時から計算開始日時を差し引いて、経過時間を出力
	fmt.Println("Elapsed: ", time.Now().Sub(started))
}

2020/08,江端さんの技術メモ

新規投稿を追加しようとすると、何を記入しようとしても「このブロックでエラーが発生したためプレビューできません」がでてきて、何もできなくなりました。

で、Webで調べたことを片っぱしから試してみたのですが(ブラウザのキャシュをクリア、wp-config.phpを修正しJavaScript連結を無効化する)、まったくダメダメでした。

原因を考えてみると、「昨日、WordPressのアップデートがあったなー」と思い、"WP Downgrade | Specific Core Version"というプラグインを入れて、WordPressのダウングレードを実施したのですが、状況が悪化しました

過去の投稿の編集画面が真っ白になって、何もできなくなりました。

で"WP Downgrade | Specific Core Version"を使って、元のバージョンに戻したのですが、別段状況が改善されている訳ではありません。

で、すったもんだしているうちに、どうやら、私が使わせて頂いているテーマ(Luxeritas, Luxeritas Child Theme)のバージョンが関係していそうだということが分かってきました。

FFFTPとか使って、いろいろ小手技(ファイルの入れ替え)とかやったのですが、全くダメだっだので、テーマの強制インストールを実施しました。

ここから、「本体」と「子テーマ」をダウンロードする。

でもって、「外観」→「テーマ」→「新規追加」→「テーマのアップロード」で

さきほどダウンロードしたファイルを、それぞれインストールする。

「すでにダウンロード済み」と言われれるが、構わずインストールを強行する。

で、理由は分からんけど、これで問題が発生しなくなりました。

まあ、釈然としないけど、WordPressのバージョンアップは、結構なリスクがあるので、これからは消極的に対応するようにします。

2020/08,江端さんの技術メモ

// mail.go

package main
 
import (
    "net/http"
 
    "github.com/labstack/echo"
)
 
func main() {
    e := echo.New()
	e.POST("/save", save)
	e.Logger.Fatal(e.Start(":1323"))
}


func save(c echo.Context) error {
	 name := c.FormValue("name")
	 email := c.FormValue("email")
	 return c.String(http.StatusOK, "name:"+name+", email:"+email)
}

でもって、curlで送ってみた。

ebata@DESKTOP-P6KREM0 MINGW64 ~
$ curl -F "name=ebata" -F "email=mailmail" http://localhost:1323/save

で、出力が、以下のようになった。
name:ebata, email:mailmail

2020/08,江端さんの技術メモ

golang ではファイル名が _test.go で終わるファイルを run すると
上記のエラーが表示されて実行できません。

ちなみに、buildだと

$ go build echo_test.go
no packages to build

と表示されて、なんのことやらさっぱり分かりませんでした。

2020/08,江端さんの技術メモ

$ go build echo.go
echo.go:4:5: cannot find package "github.com/trevex/golem" in any of:
        c:\go\src\github.com\trevex\golem (from $GOROOT)
        C:\Users\ebata\go\src\github.com\trevex\golem (from $GOPATH)

ebata@DESKTOP-P6KREM0 MINGW64 ~/go_websocket_test
$ go get -u github.com/trevex/golem

ebata@DESKTOP-P6KREM0 MINGW64 ~/go_websocket_test
$ go build echo.go