「Container」カテゴリーアーカイブ

KubernetesのCoreDNSで名前解決のログを出力する方法

Kubernetesをインストール後、Podが外部ホストと通信ができない場合、CoreDNSで名前解決できているか確認したいことがあります。本記事では、Podが外部ホストを名前解決する流れを説明すると共に、CoreDNSの名前解決のログを出力する方法を紹介します。

  • Podが外部ホストを名前解決する流れ
  • CoreDNSで名前解決のログを出力する方法

Podが外部ホストを名前解決する流れ


Podが外部ホストを名前解決する場合、デフォルト(dnsPolicyがClusterFirst)は以下の流れで名前解決します。

  1. Pod自身のlocalhostのレコードを確認
  2. CoreDNSに登録されているレコードを確認
  3. CoreDNSが稼働するサーバーで名前解決を確認

つまり、名前解決ができない場合、以下の流れで確認する必要があります。次項では、3を確認するための設定方法を記載します。

  1. PodからCoreDNSへの通信ができているか?
  2. Podの/etc/resolve.confにCoreDNSのService IPが登録されているか?
  3. CoreDNSで名前解決のログが出力されているか?
  4. CoreDNSで名前解決ができているか?
  5. サーバーで名前解決できているか?

CoreDNSで名前解決のログを出力する方法


CoreDNSのDNS設定は、以下のコマンドでConfigMapを編集することで変更できます。logを1行追加するだけで、名前解決のログを出力することができます。

$ kubectl edit cm -n kube-system coredns
apiVersion: v1
~~省略~~
data:
  Corefile: |
    .:53 {
        log ← 追加
        ~~省略~~
    }

追加すると以下のようなログが出力されるので、BusyBoxからnslookupをした時に名前解決できるか確認できます。

[INFO] 10.244.0.8:41729 - 12544 "AAAA IN [ホスト名] udp 52 false 512" NOERROR qr,rd,ra 173 0.002533234s
[INFO] 10.244.0.8:41729 - 12544 "A IN [ホスト名] udp 52 false 512" NOERROR qr,rd,ra 202 0.003439738s
[INFO] 10.244.0.8:41729 - 12544 "AAAA IN [ホスト名] udp 52 false 512" NOERROR qr,aa,rd,ra 173 0.000178813s

以上です。

マルチテナントにおけるOpenShift Service MeshによるRoute公開を検証してみた

マルチテナント環境において、OpenShift Service Meshを利用する場合の外部公開はどうすればいいか疑問を感じたので、OpenShift Service Meshを利用した場合の外部公開を検証しました。

  • OpenShiftのマルチテナント環境における外部公開(想定)
  • OpenShift Service Meshのマルチテナント環境における外部公開(想定)
  • OpenShift Service Meshの検証
    • メンバーロールの作成
    • Routeの作成
    • Gatewayの作成
  • 注意点

OpenShiftのマルチテナント環境における外部公開(想定)


マルチテナントプラットフォームにOpenShiftを採用する場合、Routeリソースを使って、それぞれのシステムを外部に公開することが想定されます。

マルチテナント管理チームがProjectリソースを作成・管理し、プロジェクトチームが各自でRouteリソースを作成することが考えられます。

※ベストプラクティスではなく、あくまでも想定です。要件・環境に応じて設計は変わることご認識いただければ幸いです。

OpenShift Service Meshのマルチテナント環境における外部公開(想定)


OpenShift Service Meshを利用する場合、Istio外部から受け付けるServiceリソースがService MeshのコントロールプレーンProjectにあります。Routeを利用して外部に公開するには、そのServiceに紐づいたRouteを作成する必要があります。

従って、RouteリソースをService MeshのコントロールプレーンProject内に作成し、各ProjectでService Meshのリソース(Gateway・VirtualService・DestinationRule)を作成する形が想定されます。

※ベストプラクティスではなく、あくまでも想定です。要件・環境に応じて設計は変わることご認識いただければ幸いです。

OpenShift Service Meshの検証


上記で想定した役割を踏まえて、実装してみました。

※VertualService/DestinationRule/Service/アプリケーションの作成は、通常のIstioと変わらないので省略しています。istio関連の操作については、Istio関連記事に投稿しています。
※OpenShift Service Meshのインストールは省略しています。インストール方法については、OpenShift Service Meshをインストールする方法に記載しています。

メンバーロールの作成


OpenShift Service Meshでは、ServiceMeshMemberRollというリソースに、Service Meshの対象プロジェクトを定義する必要があります。

以下のように、membersブロックにプロジェクト名を定義します。

apiVersion: maistra.io/v1
kind: ServiceMeshMemberRoll
metadata:
  name: default
  namespace: istio-system
  ownerReferences:
    - apiVersion: maistra.io/v1
      kind: ServiceMeshControlPlane
      name: basic-install
  finalizers:
    - maistra.io/istio-operator
spec:
  members:
    - project-a
    - project-b

Routeの作成


OpenShift Service MeshをOperatorHubからインストールすると、Istioが外部からの通信を受け付けるためのistio-ingressgateway Serviceリソースが作成されます。このServiceをRouteで外部に公開することで、クラスター外のユーザーからアクセスできるようになります。

以下のコマンドで、istio-ingressgateway に紐づくProjectAとProjectBのRouteリソースを作成します。

$ oc expose svc istio-ingressgateway --hostname=example-a.test.com --port=8080 --name=project-a --generator=route/v1
route.route.openshift.io/project-a exposed
$ oc expose svc istio-ingressgateway --hostname=example-b.test.com --port=8080 --name=project-b --generator=route/v1
route.route.openshift.io/project-b exposed

当然ながらhostnameは外部から名前解決できる必要があります。本記事ではローカルホストにexample-a.test.com, example-b.test.comを登録しました。

Gatewayの作成


各Projectで作成するGatewayを作成します。

project-aにexample-a.test.comの80番ポートで受け付けるa-gwを作成します。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: a-gw
  namespace: project-a
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "example-a.test.com"

同様にproject-bにexample-b.test.comの80番ポートで受け付けるb-gwを作成します。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: b-gw
  namespace: project-b
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "example-b.test.com"

example-a.test.com, example-b.test.comの名前でhttpリクエストを飛ばすと、それぞれのプロジェクトのアプリケーションに割り振られることが確認できました。

注意点


Gatewayリソースにおけるhostnameをワイルドカードにすると、期待していないアプリケーションに通信が飛んでしまうのでご注意ください。

名前解決や証明書のコントロールを考慮すると、RouteやGatewayはマルチテナント管理チームで作成・管理するのがいいのかもしれないなと感じました。

以上です。

kubeadmで”cri-o configured with systemd cgroup manager, but did not receive slice as parent”エラーが出たときの対処法

kubeadmを使ってkubernetesをインストールする時に、”cri-o configured with systemd cgroup manager, but did not receive slice as parent”エラーが出たときの対応方法を記します。

  • 環境情報
  • 解決方法

環境情報


本エラーが出て解決した環境情報は以下の通りです。

OS: Ubuntu 18.04 LTS
コンテナエンジン: CRI-O v1.17
kubernets関連: kubeadm v1.17, kubelet v1.17, kubectl v1.17

解決方法


本エラーは、CRI-Oはcgroupがsystemdなのに対し、kubeletのcgroupが指定されていないことで起こったエラーです。従って、kubeletのcgroupを指定する必要があります。

以下のコマンドでkubeletの設定ファイルを編集し、起動コマンドにcgroup-driverオプションを使って、systemdを指定します。

$ sudo vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
→ 以下の行に、--cgroup-driver="ststemd"を追加します。
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS --cgroup-driver="systemd"

設定後は、以下のコマンドでサービスを再起動します。

ubuntu@ip-172-31-10-183:~$ sudo systemctl daemon-reload
ubuntu@ip-172-31-10-183:~$ sudo systemctl restart kubelet

再度kubeadm initを実施すると、エラーが解消されます。

以上です。

OpenShiftでProjectとPodがTerminatingで消えない時の対処法

リソース不足が原因で、oc deleteを実行してもPodとProjectがTerminatingの状態で消えないことがありました。その時に解消したコマンドを記録します。

PodがTerminating状態で消えない時


PodがTerminatingで消えなかった時、以下のコマンドで解消しました。

$ oc delete pod [Pod名] -n [namespace名] --grace-period=0 --force

--grace-periodは、Podを正常に終了させるための期間を指定します。ここでは0を指定して、即座に終了するよう指定しています。

ProjectがTeminating状態で消えない時


PerojectがTerminatingで消えなかった時、以下の手順で解消しました。

1. Projectのマニフェストを取得する

$ oc get namespace [Project名] -o json > temp.json

2. マニフェストを編集する

取得したtemp.jsonを編集して、spec.finalizers.kubernetesの行を削除します。

    "spec": {
        "finalizers": [
            "kubernetes" ← この行を削除
        ]
    },

3. プロキシを起動する

$ oc proxy &

4. 編集したマニフェストを反映する

以下のコマンドで、編集したtemp.jsonファイルをOpenShift APIに送信します。

$ curl -H "Content-Type: application/json" -X PUT --data-binary @temp.json http://127.0.0.1:8001/api/v1/namespaces/[Project名]/finalize

このコマンドが正常に完了すると、STATUSがTerminatingだったProjectが削除されます。

以上です!

TensorFlowのチュートリアルをDockerで動かしてみる

AIOpsなどの波に押し寄せられ、機械学習について最初の一歩を恐る恐る踏み出してみました。これから始める方のために一応残しておきます。

  • TensorFlowとは
  • TensorFlowをローカルで動かす
  • TensorFlowのチュートリアルを試す

TensorFlowとは


公式ドキュメントでは、以下の通り記載があります。

TensorFlow は、機械学習向けに開発されたエンドツーエンドのオープンソース プラットフォームです。
引用元:https://www.tensorflow.org

Machine Learning(ML)を使ったアプリケーションを開発・デプロイするために作成されました。

TensorFlowをローカルで動かす


TensorFlowをローカルで動かすには、以下二通りの方法があります。本記事ではリンク先を参考に、コンテナとして実行する方法で動かします。

  • pipコマンドによるインストール
  • Dockerによるコンテナ実行

1. コンテナイメージをpullする


以下のコマンドでTensorFlowのコンテナイメージを取得します。

$ docker pull tensorflow/tensorflow:nightly-py3-jupyter

コンテナイメージにおけるタグ情報のオプションはリンク先をご参照ください。ここでは、python3のサポートとJupyterを含むナイトリービルド(最新ソースコード)のコンテナイメージを取得しています。

ちなみにJupyterとは、プログラミング言語をウェブブラウザ上で実行できるドキュメントツールです。ソースコードと説明文が混在したドキュメントを作成し、ソースコードとその実行結果をインタラクティブに表示することができます。

2. コンテナを起動する


以下のコマンドで、手順1で取得したコンテナイメージからコンテナを起動します。

$ docker run -it -p 8888:8888 tensorflow/tensorflow:nightly-py3-jupyter
[I 14:54:21.897 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
jupyter_http_over_ws extension initialized. Listening on /http_over_websocket
[I 14:54:22.459 NotebookApp] Serving notebooks from local directory: /tf
[I 14:54:22.459 NotebookApp] The Jupyter Notebook is running at:
[I 14:54:22.461 NotebookApp] http://453c162dd9e3:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d
[I 14:54:22.461 NotebookApp]  or http://127.0.0.1:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d
[I 14:54:22.463 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 14:54:22.471 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
    Or copy and paste one of these URLs:
        http://453c162dd9e3:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d
     or http://127.0.0.1:8888/?token=d233277b7f66020bbc79ea155a906ed4c41fc3846634511d

3. ブラウザからログインする


標準出力されたOr copy and paste one of these URLs配下のURLをコピーしてブラウザアクセスすると、以下の画面が表示されます。

TensorFlowのチュートリアルを試す


Dockerで動かしたJupyterを使って、TensorFlowのチュートリアル(分類問題の初歩)を試してみます。このチュートリアル自体は、機械学習のHello Worldに該当するそうです。

1. ipynbファイルを表示する


「tensorflow-tutorials」→「classification.ipynb」をクリックします。

2. 「Run」ボタンをクリックする


「Run」ボタンをクリックし、上から順にソースコードを実行していきます。それぞれの出力結果については、チュートリアルに記載されているので省略します。

チュートリアルで実施している内容をざっくり要約すると以下になります。

以上です。チュートリアルを一通りやるだけでも勉強になりました。

謎のgo starting container process caused “exec: “command”: permission denied”: unknownにハマった話

  • 事象
  • 解決方法
  • 原因について考えてみる

事象


こちらの記事で、SwaggerEditorで生成したGo-Serverをコンテナで動かすDockerfileを作成しました。

FROM golang:latest
# コンテナ作業ディレクトリの変更
WORKDIR /go/src/api
# モジュールのダウンロード
RUN go get -u github.com/gorilla/mux &&\
    go get -u "go.mongodb.org/mongo-driver/mongo"
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY ./src .
# go build
RUN go build -o api
# API実行コマンドの実行
CMD ["./api"]

ディレクトリ構造は以下の通りです。

$ tree api/
api/
├── Dockerfile
└── src
    ├── api
    │   └── swagger.yaml
    ├── go
    │   ├── README.md
    │   ├── api_user.go
    │   ├── logger.go
    │   ├── model_user.go
    │   └── routers.go
    └── main.go

上記のDockerfileをビルドして実行すると、以下のエラーが発生しました。このようなエラーは大抵ユーザー権限がない場合に出るのですが、rootユーザーでの実行のため不思議でした。

docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"./api\": permission denied": unknown.

念のためgouserに権限を持たせて実行してみましたが、事象は同じでした。

FROM golang:latest
# gouserの作成
RUN useradd -m -s /bin/bash gouser
# コンテナ作業ディレクトリの変更
WORKDIR /go/src/api
RUN chown gouser:gouser /go/src/api
# モジュールのダウンロード
RUN go get -u github.com/gorilla/mux &&\
    go get -u "go.mongodb.org/mongo-driver/mongo"
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY --chown=gouser:gouser ./src .
# go build
RUN go build -o api &&\
    chown gouser:gouser api
# gouserに変更
USER gouser
# API実行コマンドの実行
CMD ["./api"]

解決方法


rootでの実行なのにエラーが出るので試行錯誤しましたが、結果的には以下の方法で解決しました。

解決方法:ビルドファイルの名前をapiから別の名前に変更する

FROM golang:latest
# コンテナ作業ディレクトリの変更
WORKDIR /go/src/api
# モジュールのダウンロード
RUN go get -u github.com/gorilla/mux &&\
    go get -u "go.mongodb.org/mongo-driver/mongo"
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY ./src .
# go build
RUN go build -o koratta-api
# API実行コマンドの実行
CMD ["./koratta-api"]

原因について考えてみる


よく考えると、apiという名前のディレクトリが同じ階層にありました。

ディレクトリ名をapi→yamlに変更して再度実行したところ、実行ファイルがapiでもエラーが解消されました。

$ mv src/api src/yaml

以上です。まさかこんなことにハマるなんて。。。

SwaggerEditorで生成したGo API Serverの処理を実装する

  • はじめに
  • APIの全体像
  • MongoDBの作成
    • Dockerfileの作成
    • Dockerビルド
  • Go-Serverの作成
    • APIの実装
      • CreateUserメソッドの実装
      • GetUserByNameメソッドの実装
      • go/api_user.goの全体
    • Dockerfileの作成
    • Dockerビルド
  • APIの確認
    • docker-compose.yamlの作成
    • コンテナの起動
    • curlによるAPIの確認

はじめに


SwaggerEditorはOpenAPI specを様々な言語に変換することができます。以下の記事では、SwaggerEditorを使って簡単なOpenAPI specを作成し、Go-Serverに変換するところまで紹介しました。

MacにSwaggerEditorをインストールしてOpenAPI specを実装する

しかし、あくまでもAPIの雛形であって、中身の処理が実装されているわけではありません。本記事では、上記の記事で作成したGo-Serverに処理を実装します。

APIの全体像


本記事で作成するAPIの全体像について紹介します。

APIの仕様は以下の通りです。DBにはMongoDBを利用し、apiとdbはそれぞれコンテナで起動します。

  • POST /v2/user/create・・・Context-TypeでJson形式のユーザー情報を受け付け、DBに保管します。
  • GET /v2/user/{username}・・・URLに指定したusernameとマッチするレコードをDBから取得し、表示します。

MongoDBの作成


Dockerfileの作成


MongoDBのDockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree mongo/
mongo/
├── Dockerfile
├── init.sh
└── users.json

今回は揮発性のDBコンテナにします。コンテナ起動時に初期データ(users.json)を流し込むinit.shも併せて作成します。

1. 初期データの作成


コンテナ起動時に流し込む初期データをjson形式で作成します。

[
  { "username":	"koratta", "email": "koratta@example.com" },
  { "username":	"koratta2", "email": "koratta2@example.com" }
]

2. 初期実行ファイルの作成


1で作成したJsonファイルをMongoDBに保管するためのスクリプトinit.shを作成します。

mongoimport --authenticationDatabase admin --username root --password password --db api --collection users --drop --file /docker-entrypoint-initdb.d/users.json --jsonArray

3. Dockerfileの作成


init.shをコンテナ起動時に実行するmongoベースのDockerfileを作成します。コンテナ起動時に実行するため、/docker-entrypoint-initdb.dにコピーします。

FROM mongo:latest
COPY users.json /docker-entrypoint-initdb.d/
COPY init.sh /docker-entrypoint-initdb.d/

Dockerビルド


上記で作成したDockerfileをビルドし、コンテナイメージを作成します。

$ docker build -t api-mongo-db:latest mongo/

Go-Serverの作成


APIの実装


APIリクエストのルーティング情報は、go/routers.goに記述されています。

~~go/router.goより抜粋~~
var routes = Routes{ Route{ "Index", "GET", "/v2/", Index, }, Route{ "CreateUser", strings.ToUpper("Post"), "/v2/user/create", CreateUser, }, Route{ "GetUserByName", strings.ToUpper("Get"), "/v2/user/{username}", GetUserByName, }, }

/v2/user/createに対してPOSTされたらCreateUserメソッドを、/v2/user/{username}に対してGETされたらGetUserBynameメソッドを呼び出すようになっています。それぞれのメソッドはgo/api_user.goで定義されているので、それぞれを実装します。

CreateUserメソッドの実装


CreateUserメソッドを以下に示します。

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

9~11行目でPOSTされたJson形式のユーザー情報を取得しています。

	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)

12~13行目でUser構造体のinsert_recordに変換しています。

	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

このUser構造体は、OpenAPI specから生成されていて、go/model_user.goに記述されています。

package swagger

type User struct {

	Username string `json:"username,omitempty"`

	Email string `json:"email,omitempty"`
}

16~17行目でMongoDBに保管しています。getConnectメソッドはMongoDBとの接続を確立するメソッドで、go/api_user.goの全体に載せています。

	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)

GetUserByNameメソッドの実装


GetUserByNameメソッドを以下に示します。

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

5行目で検索条件となるユーザー名を取得しています。今回のAPIの仕様は、URLのパスに指定されたユーザー名の情報を表示するので、URLパスからユーザー名を取得しています。

user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

9~11行目で、URLに指定されたユーザー名を基にMongoDBから情報を取得しています。

	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)

go/user_api.goの全体


user_api.goの全体は以下の通りです。

/*
 * Koratta Test API
 *
 * This is sample api
 *
 * API version: 1.0.0
 * Contact: koratta@example.com
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package swagger

import (
	"net/http"
	"log"
	"time"
	"encoding/json"
	"context"
	"strconv"
	"strings"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/bson"
)

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

func getConnect() *mongo.Client{
	//MongoDBの認証情報
	credential := options.Credential{
		Username: "root",
		Password: "password",
	}
	//MongoDBへの接続
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	connect, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://db:27017").SetAuth(credential))
	if err != nil { log.Fatal(err) }

	return connect
}

Dockerfileの作成


Dockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree api
api
├── Dockerfile
└── src
    ├── api
    │   └── swagger.yaml
    ├── go
    │   ├── README.md
    │   ├── api_user.go
    │   ├── logger.go
    │   ├── model_user.go
    │   └── routers.go
    └── main.go

src配下のファイルは、MacにSwaggerEditorをインストールしてOpenAPI specを実装するで作成したソースコードです。

Dockerfileは以下の通りです。

FROM golang:latest
# gouserの作成
RUN useradd -m -s /bin/bash gouser
# コンテナ作業ディレクトリの変更
WORKDIR /go/src/api
RUN chown gouser:gouser /go/src/api
# モジュールのダウンロード
RUN go get -u github.com/gorilla/mux &&\
    go get -u "go.mongodb.org/mongo-driver/mongo"
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY --chown=gouser:gouser ./src .
# go build
RUN go build -o koratta-api &&\
    chown gouser:gouser api
# gouserに変更
USER gouser
# API実行コマンドの実行
CMD ["/go/src/api/koratta-api"]

必要なモジュールのgorilla/muxとmongo-driver/mongoをインストールし、gouserに実行権限を与えてgouserで実行するようにしています。root権限でも当然実行できます。

注意点は、main.goのimportパッケージのsw "go"部分です。コンテナの場合デフォルトで$GOPATH=/go/srcなので、上記のDockerfileの場合sw "api/go"になります。

APIの確認


docker-compose.yamlの作成


MongoDBとAPIのコンテナを起動するためのdocker-compose.yamlを作成します。

version: '3'
services:
  db:
    container_name: db
    image: api-mongo-db:latest
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
    ports:
      - "27017:27017"
  api:
    container_name: api
    image: api:latest
    depends_on:
      - db
    links:
      - db
    ports:
      - "8080:8080"

コンテナの起動


docker-composeを使ってコンテナを起動します。

$ docker-compose up -d

curlによるAPIの確認


作成したAPIの挙動を確認します。

初期データとして保管されているレコードをAPI経由で確認すると、以下の通り、指定したユーザー名に該当する情報を取得できます。

$ curl -X GET "http://localhost:8080/v2/user/koratta"
{"username":"koratta","email":"koratta@example.com"}

保管されていないユーザーは、当然取得できません。

$ curl -X GET "http://localhost:8080/v2/user/koratta5
$

従って、APIを使ってユーザー情報を保管します。

$ curl -X POST "http://localhost:8080/v2/user/create" -H "accept: application/xml" -H "Content-Type: application/json" -d "{ \"username\": \"koratta5\", \"email\": \"koratta5@example.com\"}"

再度確認すると、ユーザー情報を確認することができます。/v2/user/createのAPI経由でDBに保管できていることが確認できました。

$ curl -X GET "http://localhost:8080/v2/user/koratta5"
{"username":"koratta5","email":"koratta5@example.com"}

以上です。

OpenShift Service Meshをインストールする方法

  • OpenShift Service Meshとは
  • OpenShift Service Meshのコンポーネント
  • OpenShift Service Meshをインストールする

OpenShift Service Meshとは


OpenShift ServiceMeshとは、OpenShift上で稼働するマイクロアプリケーションのMesh層を接続・管理・監視するためのOpenShiftサービスです。Istioとの違いなど、OpenShift Service Meshを使うメリットは以下のURLで紹介されています。

OpenShift Service Meshを選ぶ理由

OpenShift Service Meshのコンポーネント


OpenShift Service Meshのコンポーネントは以下の通りです。必ずしも全てインストールする必要はなく、オプションで設定できます。

  • Istio・・・全体のトラフィックの管理
  • Kiali・・・トラフィックの可視化
  • Grafana・・・メトリクスの可視化
  • Jeager・・・分散トレースログの管理
  • Elasticsearch・・・分散トレースログの保管先(※Jeagerを利用する場合に、all-in-oneのインメモリ構成かElasticsearchを選択できます。)

特に注意すべき点はElasticsearchで、それなりに大きいリソースを必要としますし、永続ボリューム用のストレージが必要になります。企業では通常予算を決めてから構築しますので、予算からずれる可能性があります。

Elasticsearchの要件は、こちらをご参照ください。

OpenShift Service Meshをインストールする


GUIのOperator HubからIstioだけインストールします。

1.「OperatorHub」タブから「ServiceMesh」を検索する

2.「Install」ボタンをクリックする

3. オプションを選択する

  • Installation Mode・・・プロジェクトが利用可能な範囲を選択します。
  • Update Channel・・・インストールする対象バージョンを選択します。GA済みの安定したバージョンかTech Previewのバージョンかどうか選択することができます。
  • Approval Strategy・・・Operatorのアップデートを自動か手動で選択できます。

4. 「Installed Operators」タブからServiceMeshのStatusが「Succeeded」であることを確認し、「Istio Service Mesh Controll Plane」をクリックする

インストールが完了するとOperator Podが作成されています。

$ oc get pods -n openshift-operators
NAME READY STATUS RESTARTS AGE
istio-operator-745ffbbcbb-wrhgq 1/1 Running 0 4m36s

5. 「Create ServiceMeshControllPlane」をクリックする

6. `spec.kiali.enabled`、`spec.grafana.enabled`、`spec.tracing.enabled`をtrue→falseに変更する

7. `metadata.namespace`を作成したproject名に変更し、「Create」ボタンをクリックする

以下のコマンドでOpenShift Service Meshをデプロイするプロジェクトを作成します。

$ oc new-project istio-system

作成したプロジェクトをmetadata.namespaceに指定します。

これでインストールが完了しました。IstioのPodがデプロイされていることがわかります。

$ oc get pods -n istio-system
NAME                                     READY     STATUS    RESTARTS   AGE
istio-citadel-5b66678f8-g847h            1/1       Running   0          2m8s
istio-egressgateway-58b5ccd6b7-7flg8     1/1       Running   0          50s
istio-galley-7dddcfffd8-5ssnn            1/1       Running   0          102s
istio-ingressgateway-6b6d8d747-8cdxs     1/1       Running   0          50s
istio-pilot-c77d8b5dd-nlh4f              2/2       Running   0          64s
istio-policy-57845f5d-shhsm              2/2       Running   0          88s
istio-sidecar-injector-64c496848-dh75r   1/1       Running   0          45s
istio-telemetry-656d4d654f-m2cpq         2/2       Running   0          88s
prometheus-5467bd8998-lph9v              2/2       Running   0          119s

以上です。

MacOSにS2IコマンドをインストールしてS2Iビルドする

  • S2Iとは
  • S2Iコマンドのインストール
  • S2Iビルダーイメージのビルド
  • アプリケーションイメージのS2Iビルド

S2Iとは


S2Iとは、Source To Imageの略で、OpenShiftの特徴的な機能の一つです。その名の通り、ソースコードをコンテナイメージに変換します。S2Iビルダーイメージとソースコードからアプリケーションイメージをビルドすることによって、ビルドを自動化するだけでなく、イメージとソースコードの保守を分離することができます。

この機能における具体的なメリットは、以下の公式ドキュメントをご参照ください。
https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.4/html/builds/understanding-image-builds#build-strategy-s2i_understanding-image-builds

本記事は、S2Iツールをローカルマシン(Mac)にインストールしてローカル上で使ってみます。

S2Iコマンドのインストール


S2IコマンドをインストールしてMac PC上で操作します。

S2Iツールのパッケージは以下のURLからダウンロードできますが、Macではbrewコマンドでインストールができます。
https://github.com/openshift/source-to-image/releases/tag/v1.3.0

以下のコマンドでインストールします。

$ brew install source-to-image
$ s2i version
s2i v1.3.0-dirty

S2Iビルダーイメージのビルド


S2Iコマンドを使ってS2Iビルダーイメージをビルドしてみます。

以下のコマンドで例のようにS2Iビルダーイメージの雛形を作成することができます。指定したdirectory配下には、必要なファイルが作成されます。それぞれのファイルの解説はまた今度記事を書きたいと思いますが、自身で作成したいイメージに合わせてファイルを編集する必要があります。

$ s2i create [image名] [directory名]
例)
$ s2i create python:3.6 test-python
$ tree test-python/
test-python/
├── Dockerfile
├── Makefile
├── README.md
├── s2i
│   └── bin 
│       ├── assemble
│       ├── run
│       ├── save-artifacts
│       └── usage
└── test ←テストソースコードなので、S2Iビルダーイメージのビルドには不要です。
    ├── run
    └── test-app
        └── index.html
$ docker build -t python-s2i-builder test-python/ 
$ docker images python-s2i-builder REPOSITORY TAG IMAGE ID CREATED SIZE
python-s2i-builder latest 490710255550 3 minutes ago 426MB

今回は自分でS2Iビルダーイメージを作成するのではなく、以下のコマンドでregistry.redhat.ioのイメージを利用してアプリケーションイメージをS2Iビルドします(Red Hatのアカウントが必要になります。)。このように、OpenShiftではS2Iビルダーイメージをサポートしてくれるのが魅力の一つでもあります。

$ docker login registry.redhat.io
Username: 
Password: 
Login Succeeded
$ docker pull registry.redhat.io/rhel8/python-36
Using default tag: latest
latest: Pulling from rhel8/python-36
1a6747857d79: Pull complete 
fc5aa93e3b58: Pull complete 
dd09aac02f79: Pull complete 
8542dbe7d6e8: Pull complete 
ad98eb9d602e: Pull complete 
Digest: sha256:74057ed15aba3ba78ddf061a964b60a4e5c1d0eb8c989ad6831ea086721d9acf
Status: Downloaded newer image for registry.redhat.io/rhel8/python-36:latest
$ docker images registry.redhat.io/rhel8/python-36:latest
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
registry.redhat.io/rhel8/python-36   latest              77c14d37c369        4 weeks ago         781MB
$ docker tag registry.redhat.io/rhel8/python-36:latest python-s2i-builder
$ docker images python-s2i-builder
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
python-s2i-builder   latest              77c14d37c369        4 weeks ago         781MB

アプリケーションイメージのS2Iビルド


上記で作成したS2Iビルダーイメージを使って、アプリケーションイメージをビルドします。

以下のコマンドでアプリケーションイメージをビルドすることができます。

$ s2i build [ソースコード] [S2Iビルダーイメージ名] [アプリケーションイメージ名]

registry.redhat.ioからpullしたS2IビルダーイメージとGit上のサンプルPythonソースコードを使って、python-s2iアプリケーションイメージをビルドします。

$ s2i build https://github.com/sclorg/s2i-python-container.git --context-dir=3.6/test/setup-test-app/ python-s2i-builder python-s2i
$ docker images python-s2i
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
python-s2i          latest              b07c7f096566        15 seconds ago      789MB

アプリケーションイメージが実行できることを確認できました。

$ docker run --name hello -d -p 8080:8080 python-s2i
ae57680ef2705b92a51727bc0e6d67ce14fca3d6a515abdffc48c9f4f6913bf4
$ curl localhost:8080
Hello from gunicorn WSGI application!

以上です!

DeploymentにおけるRollingとRecreateの違い

KubernetesにおけるDeploymentのデプロイ戦略にはRollingとRecreateがあります。今日はそれぞれの違いについて共有致します。

本記事は、以下のKubernetes公式ドキュメントを参考にしています。
https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/

  • Rollingとは
  • Recreateとは
  • RollingとRecreateのユースケース

Rollingとは


Rollingとは、古いPodへのアクセスを新しく起動したPodに切り替えるデプロイ方法です。サービス提供中のPodは、新しいPodが起動するまでの間に停止することがないので、ダウンタイムを最小限に抑えることができます。

Recreateとは


Recreateは、サービス提供中のPod停止後に新しいPodを起動するデプロイ方法です。これまでのサーバー運用と同様に、アップデートにダウンタイムが発生します。

RollingとRecreateのユースケース


RollingとRecreateのユースケースを検討します。

Rollingはダウンタイムを最小限に抑えられる一方で、古いPodと新しいPodが同時に存在することになります。一方で、Recreateは新旧のPodが混在することはありませんが、ダウンタイムがRollingと比較して長い傾向にあります。

整理すると、以下のようにユースケースを分けられます。

  • Rolling・・・ダウンタイムを最小限に抑えたい場合、あるいは新旧Podの同時実行がサポートされているアプリケーションの場合に、利用することが多い。ステートレスなアプリケーションが適用しやすい。
  • Recreate・・・PersistentVolumeがRWOであったり、新旧Podの同時実行がサポートされていないアプリケーションの場合に、利用することが多い。ステートフルなアプリケーションに適用しやすい。

以上です。