Dockerでポートフォワーディング解説

##ポートフォワーディングとは

>ポートフォワーディング：
インターネットから特定のポート番号宛にパケットが届いたときに、あらかじめ 設定しておいたLAN側の機器にパケットを転送する機能です。

※引用元：https://www.itbook.info/study/nat6.html

- **ポートの紐付け**というイメージ

- [仮想環境](https://kuguru.jp/3544)で開発をする場合、この**ポートフォワーディング**が必須となると思いますが、今回は[**Docker**](https://knowledge.sakura.ad.jp/13265/)を例に少し解説します。
※今回はポートフォワーディングについてDockerをもとに話すため、Dockerについては解説しません。

##Dockerイメージ
- 概要
    - **スクリプト**
        - Go言語で作成
    - **Dockerfile**
        - golangのベースイメージを使用
- 挙動
    - コンテナを実行したら「start server」と出力
    - HTTPリクエストに対し、リクエスト側に「Hello Docker!!」と出力、リクエストを受けた側には「received request」と出力
    - 「8080」のポートを解放

```go:sample.go
package main

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

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // クライアントからリクエストを受けた場合
        log.Println("received request")
        // HTTPリクエストを受けた場合
        fmt.Fprintf(w, "Hello Docker!!")
    })

    log.Println("start server")
    server := &http.Server{Addr: ":8080"}
    if err := server.ListenAndServe(); err != nil {
        log.Println(err)
    }
}
```
```dockerfile:Dockerfile
FROM golang:1.9
RUN mkdir /echo
COPY main.go /echo
CMD ["go", "run", "/echo/main.go"]
```

##Dockerイメージのビルド
- イメージをビルドする

```terminal
$docker image build -t sample:latest .
Sending build context to Docker daemon   5.12kB
Step 1/4 : FROM golang:1.9
1.9: Pulling from library/golang
55cbf04beb70: Pull complete 
1607093a898c: Pull complete 
9a8ea045c926: Pull complete 
d4eee24d4dac: Pull complete 
9c35c9787a2f: Pull complete 
8b376bbb244f: Pull complete 
0d4eafcc732a: Pull complete 
186b06a99029: Pull complete 
Digest: sha256:8b5968585131604a92af02f5690713efadf029cc8dad53f79280b87a80eb1354
Status: Downloaded newer image for golang:1.9
 ---> ef89ef5c42a9
Step 2/4 : RUN mkdir /echo
 ---> Running in 99e306a53ced
Removing intermediate container 99e306a53ced
 ---> f9b0ec578390
Step 3/4 : COPY main.go /echo
 ---> b71ab1e040bd
Step 4/4 : CMD ["go", "run", "/echo/main.go"]
 ---> Running in fff606860921
Removing intermediate container fff606860921
 ---> 2c9096d1911b
Successfully built 2c9096d1911b
Successfully tagged sample:latest
$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
sample               latest              2c9096d1911b        16 seconds ago      750MB
```

##Dockerコンテナの実行
- [バックグラウンド](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AF%E3%82%B0%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89)でコンテナを実行
- -pオプション
    - ポートフォワーディングの設定
        - 左側（9000）が**ホストのポート**で、右側（8080）が**コンテナのポート**
            - 上記でGo言語で書かれたスクリプトを説明しましたが、8080のみ解放されている為、必然的にコンテナ側のポートは8080になります。

※ホスト側とコンテナ側のポートを分けたのは、アクセス時にどちらからアクセスしたのか、を分かりやすくする為です。

```terminal
$ docker run -d -p 9000:8080 2c9096d1911b
5e3f8c5181b99c72002fe1917044c38426132c863350c47db828909294823b0b
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
5e3f8c5181b9        2c9096d1911b        "go run /echo/main.go"   4 seconds ago       Up 3 seconds        0.0.0.0:9000->8080/tcp   objective_leavitt 
```

##HTTPアクセス
- コンテナに対して[curl](https://ja.wikipedia.org/wiki/CURL)でアクセス

```
$ curl http://localhost:9000
Hello Docker!!
```

##ここまでを図解
- Dockerfileを元に**build**してイメージを作成
- イメージを**run**してコンテナを立ち上げる
    - コンテナのステータスは**実行中**

<img width="393" alt="スクリーンショット 2019-02-16 13.22.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/267738/f5808fbf-b5af-7d8d-e729-40d14dc301ab.png">

- コンテナ実行時（**run**）、-pオプションでポートフォワーディングの設定をする
    - イメージは下図のように、コンテナに穴を開けるイメージ
        - 8080のポートを解放し、9000ポートを紐づける事で、アクセスが可能な状態した

<img width="310" alt="スクリーンショット 2019-02-16 13.40.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/267738/ccfbe0d5-64de-b60a-278d-e53e6342313b.png">

- curlで9000ポートでHTTPリクエストを送る
    - 「Hello Docker!!」とレスポンスが返ってくる

<img width="407" alt="スクリーンショット 2019-02-16 14.00.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/267738/f386e0ea-c2bc-67e5-d6ff-f797071e94a4.png">

##まとめ
こんな感じでポートフォワーディングでポートの紐付けを行ってあげる事で、仮想環境に対してもアクセスできるようになるわけです。

余談ですが、-pオプションでポートフォワーディングで設定をしましたが、もしホスト側の設定をしない場合どうなると思いますか？

```
// こんな感じでコンテナ側のポートしか設定しない場合です
$ docker run -d -p 8080 abcdefg
```

まぁ知ってる方多いと思いますが、よしなに空いているポート番号を割り振って、そのポート番号と紐付けを行ってくれます。

上記を実行した後に**docker ps**をして、PORTSを確認すると、どのポートと紐づけられているか、が分かりますので、試したことの無い方はぜひお試し下さい。