(初心者用)Docker+goを使って簡易アプリケーションを作成する

#この記事で学べること
- Dockerのイメージ、コンテナでよく使うコマンドの使い方がわかる。
- 自身で作成したコンテナを起動できる。
- Goに触れられる。


この記事の最後ではDockerfileを元に作成したイメージからコンテナを作成、起動出来るようになります！
最後まで読んで頂けたら嬉しいです。

##用意するファイル
```go:main.go

package main

import (
  "fmt"
  "log"
  "net/http"
)

func main(){
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
    log.Println("received request")
    fmt.Fprintf(w, "Hello Dcoker!!")
  })

  log.Println("start server")
  server := &http.Server{Addr: ":8080"}
  if err := server.ListenAndServe(); err !=nil {

     log.Println(err)
    }
  }

```

```docker:Dockerfile
FROM golang:1.9

RUN mkdir /echo
COPY main.go /echo
CMD ["go", "run", "/echo/main.go"]

```
※goに関しての解説はしません。
　この記事はDocker初心者を対象にしているためDockerfile内のコードについて解説します。

##FROM
FROMは作成するDockerイメージのベースとなるイメージを指定します。
Dockerfileでイメージをビルドする際、FROMでは指定されたイメージをダウンロードしてから実行されます。
FROMで取得するイメージはDocker hubとゆうレジストリに公開されているものです。

##RUN 
RUNはDockerイメージビルド時に、Dokcerコンテナ内で実行するコマンドを定義します。
RUNの引数にはDockerコンテナ内で実行するコマンドをそのまま指定します。
今回はmain.goを配置する為の/echoディレクトリをmkdirで作成しています。

##COPY
COPYはDockerを動作させているホストマシン上のファイルやディレクトリをDockerコンテナ内にコピーする為のインストラクションです。ここではホストマシン上で作成したmain.goをRUNで作成した/echoディレクトリコピーしています。

##CMD
CMDはDockerをコンテナとして実行する際に、コンテナ内で実行するプロセスを指定します。
イメージをビルドする為のRUNに対して、CMDはコンテナ起動時に一度実行されます。
RUNでアプリケーションの更新や配置、CMDでアプリケーションそのものを動作させると考えてください。
CMD ["go", "run", "/echo/main.go"]はシェルで書くと以下と同じ動作をします。

```shell:shell

$ go run /echo/main.go

```

#イメージをビルドする
以下のコマンド形式でイメージをビルドします。

```shell:shell

$ docker image build -t イメージ名[:タグ名] Dockerfile配置ディレクトリのパス

```


今回の例ではexaple/echoとゆうイメージ名にします。

以下のように表示されたらビルド成功です

```shell:shell
$docker image build -t example/echo:latest .
Sending build context to Docker daemon  10.24kB
Step 1/4 : FROM golang:1.9
 ---> ef89ef5c42a9
Step 2/4 : RUN mkdir /echo
 ---> Using cache
 ---> 4eec5940f0fc
Step 3/4 : COPY main.go /echo
 ---> 0005c6f610cf
Step 4/4 : CMD ["go", "run", "/echo/main.go"]
 ---> Running in 1f1204c5d28e
Removing intermediate container 1f1204c5d28e
 ---> d59acfee20ca
Successfully built d59acfee20ca
Successfully tagged example/echo:latest

```

※-tオプションはイメージ名、タグ名を指定する為のコマンドです。

作成されたイメージは以下のコマンドで確認できます。

```shell:shell

$docker image ls
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
example/echo         latest              d59acfee20ca        4 minutes ago       750MB
```
#コンテナを起動する

docker container run コマンドを利用してコンテナを起動します。

```shell:shell
$ docker container run -d example/echo:latest
```
今回は-dオプションをつけてコンテナをバックグラウンドで起動しています。


以下のようにハッシュ値のような文字列が出力されたらコンテナ起動成功です。

```shell:shell
$ docker container run -d example/echo:latest
6ed22edebc39a3e74d7a5c318129aa2fce5551fb1003617acc436092f80aa917
```
作成されたコンテナは以下のコマンドで確認できます。

```shell:shell
$ docker container ls
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
6ed22edebc39        example/echo:latest   "go run /echo/main.go"   8 minutes ago       Up 8 minutes                            hopeful_thompson

```

コンテナ起動時に出力されたハッシュ値のような文字列の最初の12文字がコンテナIDとして利用されます。

さてこの起動したコンテナ上のアプリケーションが正しく実行されているかをどのように確認すれば良いのでしょうか？
これを説明する為に、今回はポートフォワーディング機能について簡単に説明いたします。
###ポートフォワーディング機能
今回GO言語で書いたコードでは、このアプリケーションは8080ポートで公開されています。
ローカル環境で8080ポートに向けてcurlコマンドを使ってGETリクエストを送信してみましょう

```shell:shell
$curl http://localhost:8080
curl: (7) Failed to connect to localhost port 8080: Connection refused

```

Connection refusedが表示されました。この挙動を見る限り、アプリケーションのポートはローカル環境の8080ポートでは公開されていないようです。ここにDockerならではの特徴があります。
Dockerコンテナは仮想環境ですが、外から１つの独立したマシンのように扱える特徴があります。
echoアプリケーションは8080ポートを公開していますが、このポートはコンテナポートと呼ばれるコンテナ内に限定されたポートです。もしcurlをコンテナの中で実行するのであれば正しくレスポンスを得ることができますが、コンテナの外（今回はローカル環境）からコンテナポートを直接利用できない為Connection refusedとなるのです。
このようなHTTPリクエストを受けるアプリケーションの場合、コンテナの外から来たリクエストをコンテナ内で実行しているアプリケーションにまで到達させる必要があります。
そこで登場するのがDockerのポートフォワーディングです。ポートフォワーディングではホストマシンのポートをコンテナポートに紐づけ、コンテナ外から来た通信をコンテナポートに転送することができます。
この機能によって、コンテナポートをコンテナ外からでも利用できるようになります。
ポートフォワーディング機能を使う前に、先程実行したコンテナを以下のコマンドで停止しておきます。

```shell:shell
 $ docker container stop $(docker container ls --filter "ancestor=example/echo" -q)
```
ポートフォワーディングはdocker　container runに-pオプションで指定できます。
-pオプションは{ホスト側ポート}:{コンテナポート}の書式で記述します。
ホスト側も8080にするとイメージしづらくなってしまうので、ホスト側の9000ポートをコンテナ側の8080ポートにポートフォワーディングします。

```shell:shell
$ docker container run -d -p 9000:8080 example/echo:latest
38702a897282618660d1d96e2301c6f5be3e4615e8babd8a2b171f42b05ed884
```

コンテナが実行されたらホスト側ポート、すなわちlocalhostの9000ポートにcurlでGETリクエストを送信してみましょう。


```shell:shell

 $curl http://localhost:9000
Hello Dcoker!!

```
上記のように「Hello Dcoker!!」と出力されたら、しっかりポートフォワーディングによってホスト側の９０００ポートと関連づけられているコンテナ内の8080ポートにGETリクエストを送り、レスポンスを得ることができたということになります。

http://localhost:9000  をブラウザーの検索窓で検索すると同様に「Hello Dcoker!!」と表示されると思います。

[参考図書　Docker/kubernetes実践コンテナ開発入門](
https://www.amazon.co.jp/Docker-Kubernetes-%E5%AE%9F%E8%B7%B5%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E9%96%8B%E7%99%BA%E5%85%A5%E9%96%80-%E5%B1%B1%E7%94%B0-%E6%98%8E%E6%86%B2/dp/4297100339/ref=sr_1_1?adgrpid=50113670541&gclid=CjwKCAjw_MnmBRAoEiwAPRRWW_N3mI6rJVGGOeZE3rKJ57-FlB7ixiHwCJz8MJmrYGJ9cm1RT_gS0BoCn70QAvD_BwE&hvadid=338539067959&hvdev=c&hvlocphy=1009247&hvnetw=g&hvpos=1t1&hvqmt=b&hvrand=10835942176646325467&hvtargid=kwd-496136850930&hydadcr=27263_11561109&jp-ad-ap=0&keywords=%E5%AE%9F%E8%B7%B5%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E9%96%8B%E7%99%BA%E5%85%A5%E9%96%80&qid=1557374861&s=gateway&sr=8-1)