- 投稿日:2020-06-30T23:58:06+09:00
Kabaneroを使ったアプリケーションの開発フローを確認してみた。
はじめに
勉強用にKabaneroを使ったアプリケーションの開発フロー(ローカル環境はDocker、本番環境はOpenShift)を作りながら動作確認した時の内容をまとめてみました。
Kabaneroとは
Kabaneroは、マイクロサービスベースのフレームワークで、開発者、アーキテクト、および運用チームがより迅速に連携できるしくみを作るIBM発のOSSのプロジェクトです。
Kabaneroは IBM Cloud Pak for ApplicationsにAccelerator for Teamsという名称で同梱され主要な機能になっています。
Kabanero がアップストリームプロジェクトで Kabanero → Cloud Pak for Applications(Accelerator for Teams)の順番でリリースされます。Appsody
KabaneroのアンブレラプロジェクトであるappsodyがDockerの開発ライフサイクルを抽象化しています。
そのため、開発者はdockerのコマンドやDockerfileなど知らなくても開発することができます。例えば、
- appsody cliコマンドを使ってDockerを使ったローカル環境での開発からKubernetes、OpenShiftへのデプロイまで制御します。
- stackというNode.js、Eclipse Microprofile、Quarkus、Spring Boot などのオープン・ソースのランタイムとフレームワークに対応する事前構成されたテンプレート(ベースコンテナイメージはRHEL8などのUBIイメージ)用意されています。
Kabaneroを使った開発者のワークフロー
では、Kabaneroを使うと開発者のワークフローはどのように変わるのでしょうか
フロー図に沿って、手順を確認していきたいと思います。
- 前提
- appsodyがインストールされていること
- ローカル環境にDockerがインストールされていること
- (本番環境のリリース用)OpenShiftに Kabaneroがインストールされていること
Appsody Hubから stackの一覧を取得
開発者のJaneは appsody listコマンドを使用して、利用できる stackの一覧を取得します。
appsody list REPO ID VERSION TEMPLATES DESCRIPTION kabanero java-microprofile [Deprecated] 0.2.27 *default Eclipse MicroProfile on Open Liberty & OpenJ9 using Maven kabanero java-openliberty 0.2.3 *default Open Liberty & OpenJ9 using Maven kabanero java-spring-boot2 0.3.29 *default, kafka, kotlin Spring Boot using OpenJ9 and Maven kabanero nodejs 0.3.6 *simple Runtime for Node.js applications kabanero nodejs-express 0.4.8 kafka, scaffold, *simple Express web framework for Node.js
REPO列がリポジトリ名、IDが stack名、TEMPLATESはアプリケーションのソースコードのテンプレート名です。
appsodyのプロジェクトの作成
appsody listコマンドで出力されたstackの中から、今回は java-openliberty stackを使って開発したいと思います。
まず、プロジェクトの保存先のディレクトリを作成します。
mkdir sample-application cd sample-application
そして、appsody init "stack名"を実行して、開発環境を作成します。
# appsody init kabanero/java-openliberty Checking stack requirements... Docker requirements met Appsody requirements met Running appsody init... Downloading java-openliberty template project from https://github.com/appsody/stacks/releases/download/java-openliberty-v0.2.12/incubator.java-openlibert y.v0.2.12.templates.default.tar.gz Download complete. Extracting files from /root/sample-application/java-openliberty.tar.gz Setting up the development environment Your Appsody project name has been set to sample-application Pulling docker image docker.io/appsody/java-openliberty:0.2 Running command: docker pull docker.io/appsody/java-openliberty:0.2 0.2: Pulling from appsody/java-openliberty ee2244abc66f: Pulling fs layer befb03b11956: Pulling fs layer 4d3fb84d4d60: Pulling fs layer d0aba8033c85: Pull complete e7e883ba65bb: Pull complete Digest: sha256:4dff3ca3d27b910b8dacf84895f5763d96df9671b7bbd5694ae14b4624d47dbd Status: Downloaded newer image for appsody/java-openliberty:0.2 docker.io/appsody/java-openliberty:0.2 [Warning] The stack image does not contain APPSODY_PROJECT_DIR. Using /project Running command: docker run --rm --entrypoint /bin/bash docker.io/appsody/java-openliberty:0.2 -c "find /project -type f -name .appsody-init.sh" Extracting project from development environment [Warning] The stack image does not contain APPSODY_PROJECT_DIR. Using /project Running command: docker create --name sample-application-extract -v /root/.m2/repository:/mvn/repository -v /root/sample-application/:/project/user-app docker.io/appsody/java-openliberty:0.2 Running command: docker cp sample-application-extract:/project /root/.appsody/extract/sample-application Project extracted to /root/sample-application/.appsody_init Running command: docker rm sample-application-extract -f Running command: ./.appsody-init.sh [InitScript] [INFO] Scanning for projects... [InitScript] [INFO] [InitScript] [INFO] --------------------< dev.appsody:java-openliberty >-------------------- [InitScript] [INFO] Building java-openliberty 0.2.14 [InitScript] [INFO] --------------------------------[ pom ]--------------------------------- [InitScript] [INFO] [InitScript] [INFO] --- maven-enforcer-plugin:3.0.0-M3:enforce (default-cli) @ java-openliberty --- [InitScript] [INFO] Skipping Rule Enforcement. [InitScript] [INFO] [InitScript] [INFO] --- maven-install-plugin:2.4:install (default-install) @ java-openliberty --- [InitScript] [INFO] Installing /root/sample-application/.appsody_init/pom.xml to /root/.m2/repository/dev/appsody/java-openliberty/0.2.14/java-openliberty-0.2.14.pom [InitScript] [INFO] ------------------------------------------------------------------------ [InitScript] [INFO] BUILD SUCCESS [InitScript] [INFO] ------------------------------------------------------------------------ [InitScript] [INFO] Total time: 1.354 s [InitScript] [INFO] Finished at: 2020-07-01T03:24:37+09:00 [InitScript] [INFO] ------------------------------------------------------------------------ Successfully added your project to /root/.appsody/project.yaml Your Appsody project ID has been set to 20200701032437.87801097 Successfully initialized Appsody project with the kabanero/java-openliberty stack and the default template.initが終了した後、sample-applicationディレクトリ構成は以下の通りです。
. ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main │ ├── java │ │ └── dev │ │ └── appsody │ │ └── starter │ │ ├── health │ │ │ ├── StarterLivenessCheck.java │ │ │ └── StarterReadinessCheck.java │ │ ├── StarterApplication.java │ │ └── StarterResource.java │ ├── liberty │ │ └── config │ │ ├── configDropins │ │ │ └── defaults │ │ │ └── quick-start-security.xml │ │ └── server.xml │ └── webapp │ ├── index.html │ └── WEB-INF │ └── beans.xml └── test └── java └── it └── dev └── appsody └── starter ├── EndpointTest.java └── HealthEndpointTest.java開発者Janeは、この構成をベースに開発を進めます。
ローカル環境でのアプリケーションの動作確認
開発者Janeは、アプリケーションの開発の途中、任意のタイミングでアプリケーションを起動して動作確認するのには
appsody では run / test / debug コマンドを使用てJaneのアプリケーションを起動します。では、 appsody run コマンドを実行して、アプリケーションを起動します。
# appsody run Running development environment... Pulling docker image docker.io/appsody/java-openliberty:0.2 Running command: docker pull docker.io/appsody/java-openliberty:0.2 0.2: Pulling from appsody/java-openliberty Digest: sha256:4dff3ca3d27b910b8dacf84895f5763d96df9671b7bbd5694ae14b4624d47dbd Status: Image is up to date for appsody/java-openliberty:0.2 docker.io/appsody/java-openliberty:0.2 [Warning] The stack image does not contain APPSODY_DEPS Running command: docker run --rm -p 7777:7777 -p 9080:9080 -p 9443:9443 --name sample-application -u 0:0 -e "APPSODY_USER=0" -e "APPSODY_GROUP=0" -v /root/.m2/repository:/mvn/repository -v /root/sample-application/:/project/user-app -v appsody-controller-0.3.5:/.appsody -t --entrypoint /.appsody/appsody-controller docker.io/appsody/java-openliberty:0.2 "--mode=run" [Container] Running APPSODY_PREP command: /project/run-stack.sh prep [Container] Installing parent dev.appsody:java-openliberty:0.2.14 [Container] [INFO] Scanning for projects... [Container] [INFO] [Container] [INFO] --------------------< dev.appsody:java-openliberty >-------------------- [Container] [INFO] Building java-openliberty 0.2.14 [Container] [INFO] --------------------------------[ pom ]--------------------------------- [Container] [INFO] [Container] [INFO] --- maven-enforcer-plugin:3.0.0-M3:enforce (default-cli) @ java-openliberty --- [Container] [INFO] Skipping Rule Enforcement. [Container] [INFO] [Container] [INFO] --- maven-install-plugin:2.4:install (default-install) @ java-openliberty --- [Container] [INFO] Installing /project/user-app/../pom.xml to /mvn/repository/dev/appsody/java-openliberty/0.2.14/java-openliberty-0.2.14.pom [Container] [INFO] ------------------------------------------------------------------------ [Container] [INFO] BUILD SUCCESS [Container] [INFO] ------------------------------------------------------------------------ [Container] [INFO] Total time: 0.932 s (省略) [Container] [INFO] [AUDIT ] CWWKE0001I: The server defaultServer has been launched. [Container] [INFO] [AUDIT ] CWWKG0093A: Processing configuration drop-ins resource: /opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/quick-start-security.xml [Container] [INFO] [AUDIT ] CWWKZ0058I: Monitoring dropins for applications. [Container] [INFO] [AUDIT ] CWPKI0820A: The default keystore has been created using the 'keystore_password' environment variable. [Container] [INFO] [AUDIT ] CWWKS4104A: LTPA keys created in 3.284 seconds. LTPA key file: /opt/ol/wlp/usr/servers/defaultServer/resources/security/ltpa.keys [Container] [INFO] [AUDIT ] CWPKI0803A: SSL certificate created in 4.596 seconds. SSL key file: /opt/ol/wlp/usr/servers/defaultServer/resources/security/key.p12 [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/health/ [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/ibm/api/ [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/jwt/ [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/openapi/ui/ [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/metrics/ [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/openapi/ [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/ [Container] [INFO] [AUDIT ] CWWKZ0001I: Application starter-app started in 18.902 seconds. [Container] [INFO] [AUDIT ] CWWKF0012I: The server installed the following features: [appSecurity-2.0, cdi-2.0, concurrent-1.0, distributedMap-1.0, jaxrs-2.1, jaxrsClient-2.1, jndi-1.0, json-1.0, jsonb-1.0, jsonp-1.1, jwt-1.0, microProfile-3.2, mpConfig-1.3, mpFaultTolerance-2.0, mpHealth-2.1, mpJwt-1.1, mpMetrics-2.2, mpOpenAPI-1.1, mpOpenTracing-1.3, mpRestClient-1.3, opentracing-1.3, servlet-4.0, ssl-1.0]. [Container] [INFO] [AUDIT ] CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 49.562 seconds. [Container] [INFO] CWWKM2015I: Match number: 1 is [6/30/20 21:47:36:813 UTC] 0000002e com.ibm.ws.kernel.feature.internal.FeatureManager A CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 49.562 seconds.. [Container] [INFO] Tests will run automatically when changes are detected. [Container] [INFO] Source compilation was successful. [Container] [INFO] Tests compilation was successful. [Container] [INFO] [AUDIT ] CWWKT0017I: Web application removed (default_host): http://023d8b3431cd:9080/ [Container] [INFO] [AUDIT ] CWWKZ0009I: The application starter-app has stopped successfully. [Container] [INFO] Running unit tests... [Container] [INFO] Unit tests finished. [Container] [INFO] Waiting up to 30 seconds to find the application start up or update message... [Container] [INFO] CWWKM2010I: Searching for (CWWKZ0001I.*|CWWKZ0003I.*starter-app) in /opt/ol/wlp/usr/servers/defaultServer/logs/messages.log. This search will timeout after 30 seconds. [Container] [INFO] CWWKM2015I: Match number: 1 is [6/30/20 21:47:36:038 UTC] 00000039 com.ibm.ws.app.manager.AppMessageHelper A CWWKZ0001I: Application starter-app started in 18.902 seconds.. [Container] [INFO] Running integration tests... [Container] [INFO] [AUDIT ] CWWKT0016I: Web application available (default_host): http://023d8b3431cd:9080/ [Container] [INFO] [AUDIT ] CWWKZ0003I: The application starter-app updated in 2.628 seconds. [Container] [INFO] [Container] [INFO] ------------------------------------------------------- [Container] [INFO] T E S T S [Container] [INFO] ------------------------------------------------------- [Container] [INFO] Running it.dev.appsody.starter.HealthEndpointTest [Container] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.048 s - in it.dev.appsody.starter.HealthEndpointTest [Container] [INFO] Running it.dev.appsody.starter.EndpointTest [Container] [INFO] [WARNING ] CWMOT0010W: OpenTracing cannot track JAX-RS requests because an OpentracingTracerFactory class was not provided or client libraries for tracing backend are not in the class path. [Container] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.056 s - in it.dev.appsody.starter.EndpointTest [Container] [INFO] [Container] [INFO] Results: [Container] [INFO] [Container] [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [Container] [INFO] [Container] [INFO] Integration tests finished.ログからは、docker pull、maven、OpenLibertyの起動、IntegrationTestの実行されているのがわかりますが、
OpenLibertyが起動したので、 起動確認として、healthチェックがupになっているのを確認します。# curl http://localhost:9080/health {"checks":[{"data":{},"name":"StarterLivenessCheck","status":"UP"},{"data":{},"name":"StarterReadinessCheck","status":"UP"}],"status":"UP"}これで、ローカル環境でのアプリケーションの起動を確認することができました。
Janeは開発が終了まで、ソースコードの作成とappsody runコマンドで動作確認を繰り返すことになります。OpenShiftへリリースするためのアプリケーションのビルド
Janeはアプリケーションの開発が終了したので、OpenShiftへデプロイします。
まずは、appsody buildコマンドを実行して、本番環境用のコンテナイメージをビルドします。
appsody runの時はローカル環境用のbuilderを使って、コンテナイメージを生成していましたが、appsody buildの場合は本番環境のbuilderを使ってコンテナイメージを生成します。# appsody build Extracting project from development environment Pulling docker image docker.io/appsody/java-openliberty:0.2 Running command: docker pull docker.io/appsody/java-openliberty:0.2 0.2: Pulling from appsody/java-openliberty Digest: sha256:4dff3ca3d27b910b8dacf84895f5763d96df9671b7bbd5694ae14b4624d47dbd Status: Image is up to date for appsody/java-openliberty:0.2 docker.io/appsody/java-openliberty:0.2 [Warning] The stack image does not contain APPSODY_PROJECT_DIR. Using /project Running command: docker create --name sample-application-extract -v /root/.m2/repository:/mvn/repository -v /root/sample-application/:/project/user-app docker.io/appsody/java-openliberty:0.2 Running command: docker cp sample-application-extract:/project /root/.appsody/extract/sample-application Project extracted to /root/.appsody/extract/sample-application Running command: docker rm sample-application-extract -f (省略) [Docker] Sending build context to Docker daemon 706kB [Docker] Step 1/62 : FROM kabanero/ubi8-maven:0.9.0 as compile [Docker] 0.9.0: Pulling from kabanero/ubi8-maven (省略) [Docker] Removing intermediate container 587544680ab9 [Docker] ---> 6cb69d5ff953 [Docker] Successfully built 6cb69d5ff953 [Docker] Successfully tagged dev.local/sample-application:latest Built docker image dev.local/sample-application Running command: docker create --name sample-application-extract docker.io/appsody/java-openliberty:0.2 Running command: docker cp sample-application-extract:/config/app-deploy.yaml /root/sample-application/app-deploy.yaml Running command: docker rm sample-application-extract -f Created deployment manifest: /root/sample-application/app-deploy.yamlOpenShiftへのデプロイは OpenShiftにインストールされた Appsody Operatorがアプリケーションをデプロイします。
最後に作成されたapp-deploy.yamlは、Appsody Operatorに渡すためのmanifestです。
- app-deploy.yaml
apiVersion: openliberty.io/v1beta1 kind: OpenLibertyApplication metadata: annotations: architecture: x86_64 authoritative-source-url: registry.access.redhat.com build-date: 2020-03-31T14:54:13.907559 commit.stack.appsody.dev/contextDir: /incubator/java-openliberty commit.stack.appsody.dev/date: Thu Jun 4 13:29:00 2020 +0100 commit.stack.appsody.dev/message: 'java-openliberty: define APPSODY_DEBUG_PORT (#818)' description: Eclipse MicroProfile & Jakarta EE on Open Liberty & OpenJ9 using Maven distribution-scope: public image.opencontainers.org/created: "2020-07-01T07:13:29+09:00" k8s.io/description: The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly. k8s.io/display-name: Red Hat Universal Base Image 8 maintainer: Red Hat, Inc. name: java-openliberty openshift.io/expose-services: "" openshift.io/tags: base rhel8 redhat.com/build-host: cpt-1007.osbs.prod.upshift.rdu2.redhat.com redhat.com/component: ubi8-container redhat.com/license_terms: https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI release: "408" stack.appsody.dev/authors: Mike Andrasak <uberskigeek>, Andy Mauer <ajm01>, Scott Kurz <scottkurz>, Adam Wisniewski <awisniew90> stack.appsody.dev/configured: docker.io/appsody/java-openliberty:0.2 stack.appsody.dev/created: "2020-06-04T12:31:59Z" stack.appsody.dev/description: Eclipse MicroProfile & Jakarta EE on Open Liberty & OpenJ9 using Maven stack.appsody.dev/digest: sha256:4dff3ca3d27b910b8dacf84895f5763d96df9671b7bbd5694ae14b4624d47dbd stack.appsody.dev/documentation: https://github.com/appsody/stacks/tree/master/incubator/java-openliberty/README.md stack.appsody.dev/licenses: Apache-2.0 stack.appsody.dev/revision: 1b105b4891a3edef718d668271da30086214dd84 stack.appsody.dev/source: https://github.com/appsody/stacks/tree/master/incubator/java-openliberty/image stack.appsody.dev/tag: docker.io/appsody/java-openliberty:0.2.14 stack.appsody.dev/title: Open Liberty stack.appsody.dev/url: https://github.com/appsody/stacks/tree/master/incubator/java-openliberty summary: Open Liberty url: https://access.redhat.com/containers/#/registry.access.redhat.com/ubi8/images/8.1-408 vcs-ref: 26f36bfa3e3a04c8c866b250924c1aefc34f01c9 vcs-type: git vendor: Open Liberty version: 0.2.14 creationTimestamp: null labels: image.opencontainers.org/title: sample-application stack.appsody.dev/id: java-openliberty stack.appsody.dev/version: 0.2.14 name: sample-application namespace: sampleapps1 spec: applicationImage: dev.local/sample-application createKnativeService: false expose: true livenessProbe: failureThreshold: 12 httpGet: path: /health/live port: 9080 initialDelaySeconds: 5 periodSeconds: 2 readinessProbe: failureThreshold: 12 httpGet: path: /health/ready port: 9080 initialDelaySeconds: 5 periodSeconds: 2 timeoutSeconds: 1 service: annotations: prometheus.io/scrape: "true" port: 9080 type: NodePort version: 1.0.0アプリケーションのOpenShiftへのデプロイ
buildが終了すると本番環境のコンテナイメージが生成されます。このイメージをOpenShiftにデプロイするためには appsody deployコマンドでデプロイします。
その前に、OpenShiftの環境を準備します。
プロジェクト(namespace)の作成
アプリケーションをデプロイするためのプロジェクト( sampleapps )を作成します。
# oc new-project sampleapps Now using project "sampleapps" on server "https://api.lab.example.com:6443". You can add applications to this project with the 'new-app' command. For example, try: oc new-app ruby~https://github.com/sclorg/ruby-ex.git to build a new example application in Python. Or use kubectl to deploy a simple Kubernetes application: kubectl create deployment hello-node --image=gcr.io/hello-minikube-zero-install/hello-node現時点では、 sampleappsプロジェクトには何もリソースはありません。
# oc get all -n sampleapps No resources found in sampleapps namespace.次に"oc get csv"コマンドを実行すると、sampleappsプロジェクトでは以下のOperatorが使えるようになっていますが、
このOperatorは、KabaneroをInstallしたときに導入されたOperatorが、sampleappsプロジェクトにも展開されています。# oc get csv -n sampleapps NAME DISPLAY VERSION REPLACES PHASE appsody-operator.v0.5.1 Appsody Operator 0.5.1 Succeeded elasticsearch-operator.4.3.26-202006160135 Elasticsearch Operator 4.3.26-202006160135 elasticsearch-operator.4.3.25-202006081335 Succeeded jaeger-operator.v1.17.3 Red Hat OpenShift Jaeger 1.17.3 jaeger-operator.v1.17.2 Succeeded kiali-operator.v1.12.13 Kiali Operator 1.12.13 kiali-operator.v1.12.12 Succeeded open-liberty-operator.v0.5.1 Open Liberty Operator 0.5.1 Succeeded openshift-pipelines-operator.v0.11.2 OpenShift Pipelines Operator 0.11.2 Succeeded serverless-operator.v1.7.1 OpenShift Serverless Operator 1.7.1 Succeeded servicemeshoperator.v1.1.3 Red Hat OpenShift Service Mesh 1.1.3 servicemeshoperator.v1.1.2.3 Succeeded次に、OpenShiftの内部レジストリに本番環境用のコンテナイメージpushするために、内部レジストリにログインします。
ログインするために必要な内部レジストリのURLを変数HOSTに格納します。
HOST=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}') # echo ${HOST} default-route-openshift-image-registry.apps.lab.example.comそして、内部レジストリにログインして、ローカル環境からOpenShiftにコンテナイメージをpushできるようにします。
# docker login -u kubeadmin -p $(oc whoami -t) $HOST WARNING! Using --password via the CLI is insecure. Use --password-stdin. WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded最後に appsody deployコマンドを使用して、OpenShiftにデプロイします。
# appsody deploy --tag sampleapps/sample-application:v1 --push-url $HOST --push --pull-url docker-registry.default.svc:5000 --namespace sampleapps-- tag プロジェクト名/コンテナイメージ名:tag
--push-url $HOST --push は ローカル環境からpushするためにアドレスを記述
--pull-url は OpenShift内部で、デプロイするのに pullするために必要なURLを記述デプロイが終了して、"oc get all -n sampleapps" コマンドを実行すると podが起動しているのが確認できます。
# oc get all -n sampleapps NAME READY STATUS RESTARTS AGE pod/sample-application-6d56c87cdf-fhrs2 0/1 Running 0 42s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/sample-application NodePort 172.30.145.68 <none> 9080:30957/TCP 43s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/sample-application 0/1 1 0 42s NAME DESIRED CURRENT READY AGE replicaset.apps/sample-application-6d56c87cdf 1 1 0 42s NAME IMAGE REPOSITORY TAGS UPDATED imagestream.image.openshift.io/sample-application default-route-openshift-image-registry.apps.lab.example.com/sampleapps/sample-application latest 2 minutes ago NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD route.route.openshift.io/sample-application sample-application-sampleapps.apps.lab.example.com sample-application 9080-tcp None外部からcurlでhealthチェックのURLにアクセスすると statusがupになってるのが確認できました。
# curl http://sample-application-sampleapps.apps.lab.example.com/health {"checks":[{"data":{},"name":"StarterLivenessCheck","status":"UP"},{"data":{},"name":"StarterReadinessCheck","status":"UP"}],"status":"UP"}Kabaneroを使った開発フローのまとめ
これまでのkabaneroを使ったアプリケーションの開発フローをまとめると下図の様ながれになります。
これまでの流れから、appsody cliのコマンドを使うとdockerコマンドが抽象化されているので、開発者はdockerの作法知らなくても、開発できそうです。
また、stackをカスタマイズすると、それぞれの組織にあったstackもできそうです。
そして、5. deployでは、appsody operatorがアプリケーションをOpenShiftにデプロイしているのは初見でした。
もう少し勉強をすすめていきたいと思います。
- 投稿日:2020-06-30T23:28:00+09:00
「Docker/Kubernetes 実践コンテナ開発入門」の7章をHelm v3でやってみた
はじめに
「Docker/Kubernetes 実践コンテナ開発入門」を読みながらKubernetesを勉強していたのですが、コンテナ技術の発展が早く、出版が2018年8月ということで内容が少し古くなってきました。特に7章3節のHelmの解説では、Helmのバージョンがv31に上がったことによるコマンド変更があり、Helm v3を使用する場合には手順がそのまま使えなくなっています。この記事ではHelm v3を使用した場合の手順について解説します。
前提
- 「Docker/Kubernetes 実践コンテナ開発入門」を購入し、読んでいる方向けです。
- あくまで差分の紹介なので、技術の解説等は本を読んでください。
環境
- mac OS Catalina 10.15.5
- プロセッサ: 2.3 GHz デュアルコアIntel Core i5
- メモリ: 8 GB 2133 MHz LPDDR3
dockerはdocker for macを使用してローカルで実行しました。
$ docker --version Docker version 19.03.8, build afacb8b $ kubectl version Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.4", GitCommit:"c96aede7b5205121079932896c4ad89bb93260af", GitTreeState:"clean", BuildDate:"2020-06-18T02:59:13Z", GoVersion:"go1.14.3", Compiler:"gc", Platform:"darwin/amd64"} Server Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.6-beta.0", GitCommit:"e7f962ba86f4ce7033828210ca3556393c377bcc", GitTreeState:"clean", BuildDate:"2020-01-15T08:18:29Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"linux/amd64"}また、VS Codeでyamlを編集していたのですが、自動整形をした際の中括弧の挙動がおかしかったので、VS Code拡張機能の
vscode-helm
をインストールしました。手順
Helmのセットアップ
kubectlをローカルkubernetesクラスタに切り替えておきます。
HomebrewでHelmをインストールします。$ brew install helm $ helm version version.BuildInfo{Version:"v3.2.4", GitCommit:"0ad800ef43d3b826f31a5ad8dfbb4fe05d143688", GitTreeState:"dirty", GoVersion:"go1.14.3"}v3でTillerサーバが廃止されたので、
helm init
は不要です。
v2ではkubernetesクラスタ上にTillerサーバがデプロイされ、クライアントのhelmコマンドでTillerサーバのAPIを呼び出し、命令を受けたTillerサーバがkubernetesクラスタのAPIを呼び出すという流れになっていました。拡張性のある構成だとは思いますが、Tillerサーバ周りのサービス・アカウントの設定が複雑になるということで使い勝手が悪かったようです。v3 ではTillerサーバが廃止され、クライアントから直接kubernetesクラスタのAPIを呼び出すようになりました。マニフェストの生成もクライアントで行われるようになっています。詳細は下記のサイトをご覧ください。Helmの概念
v2とは違いstableリポジトリはデフォルトでは利用できなくなっています。次のようにすればstableリポジトリを追加できます。
$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/
stableリポジトリは将来廃止される予定で、分散型のリポジトリに移行するようです。
The stable repository is no longer added by default. This repository will be deprecated during the life of Helm v3 and we are now moving to a distributed model of repositories that can be searched by the Helm Hub.
Ref: https://helm.sh/blog/helm-v3-beta/リポジトリの検索は
helm search
で行いますが、repo
とhub
の2つで検索を指定します。hubの検索は一部のリポジトリのみが表示されます。そのリポジトリからChartを使用するには、関連するリポジトリをHelmリポジトリリストに追加する必要があります。$ helm search -h Search provides the ability to search for Helm charts in the various places they can be stored including the Helm Hub and repositories you have added. Use search subcommands to search different locations for charts. Usage: helm search [command] Available Commands: hub search for charts in the Helm Hub or an instance of Monocular repo search repositories for a keyword in charts Flags: ...Chartをインストールする
redmineをインストールします。
bitnami
リポジトリを追加します。一応リポジトリの情報を最新化しておきます。$ helm repo add bitnami https://charts.bitnami.com/bitnami $ helm repo updateインストールするChartの情報です。
$ helm show chart bitnami/redmine helm show chart bitnami/redmine apiVersion: v1 appVersion: 4.1.1 dependencies: - condition: mariadb.enabled name: mariadb repository: https://charts.bitnami.com/bitnami version: 7.x.x - condition: postgresql.enabled name: postgresql repository: https://charts.bitnami.com/bitnami version: 8.x.x description: A flexible project management web application. home: http://www.redmine.org/ icon: https://bitnami.com/assets/stacks/redmine/img/redmine-stack-220x234.png keywords: - redmine - project management - www - http - web - application - ruby - rails maintainers: - email: containers@bitnami.com name: Bitnami name: redmine sources: - https://github.com/bitnami/bitnami-docker-redmine version: 14.2.4
インストールします。パラメータを
redmine.yaml
で定義します。redmine.yamlredmineUsername: abeT redminePassword: abeT redmineLanguage: ja service: type: NodePortリリース名は
--name
で指定せず、helm install
の直後に指定します。$ helm install -f redmine.yaml redmine bitnami/redmine NAME: redmine LAST DEPLOYED: Fri Jun 26 15:42:19 2020 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: 1. Get the Redmine URL: export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services redmine) export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}") echo "Redmine URL: http://$NODE_IP:$NODE_PORT/" 2. Login with the following credentials echo Username: abeT echo Password: $(kubectl get secret --namespace default redmine -o jsonpath="{.data.redmine-password}" | base64 --decode)インストールされたものの一覧を表示します。
$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION redmine default 1 2020-06-26 13:55:26.228257 +0900 JST deployed redmine-14.2.4 4.1.1作成されたリソースを確認します。
$ kubectl get service,deployment NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24m service/redmine NodePort 10.97.203.207 <none> 80:30677/TCP 114s service/redmine-mariadb ClusterIP 10.110.204.194 <none> 3306/TCP 114s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/redmine 1/1 1 1 114s
service/redmine
がNodePort
になっており、http://localhost:30677
でredmineにアクセスできるようになっています。※ deployment.apps/redmineがREADYにならず、止まってしまう事象が発生しました。原因は不明ですが(多分メモリの不足?)、
Reset Kubernetes Cluster
でクラスターを全削除してから実行するとうまくいく場合があります。アンインストール
次のコマンドでアンインストールを実行できます。v3ではデフォルトではリビジョンの記録が残りません。
--keep-history
を付与することで、リビジョンを残しておくことができます。$ helm uninstall redmine --keep-history release "redmine" uninstalledリビジョンを確認できます。
$ helm ls --all NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION redmine default 1 2020-06-26 16:14:29.972481 +0900 JST uninstalled redmine-14.2.4 4.1.1ロールバックを実行します。
$ helm rollback redmine 1 Rollback was a success! Happy Helming!
削除がロールバックされて、実行状態になっています。
$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION redmine default 2 2020-06-29 14:21:46.552373 +0900 JST deployed redmine-14.2.4 4.1.1リビジョンの記録を残さないで削除するには
--keep-history
なしで削除します。$ helm uninstall redmine release "redmine" uninstalled $ helm ls --all NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSIONChartを自作する
Chartのテンプレートを作成します。
$ helm create echo Creating echo $ tree . . └── echo ├── Chart.yaml ├── charts ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ └── tests │ └── test-connection.yaml └── values.yaml 4 directories, 10 filesMacには標準で
tree
コマンドは入っていないので、Homebrewでインストールしました。$ brew install treev2と比べると、次の3つファイルが増えています。これらのファイルは今回は使用しません。
- hpa.yaml # HorizontalPodAutoscalerのマニフェスト
- serviceaccount.yaml # ServiceAccountのマニフェスト
- test-connection.yaml # Chartをテストするのに使用する
Chartのテンプレートの構成はこちらを参照してください。
生成された
deployment.yaml
の中身は次のようになっています。deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: {{ include "echo.fullname" . }} labels: {{- include "echo.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "echo.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "echo.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "echo.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }}あらかじめ様々なパラメータが抽象化されています。開発者が編集するのは
spec.template.spec
の部分です。次のように変更します。deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: {{ include "echo.fullname" . }} labels: app: {{ include "echo.name" . }} chart: {{ include "echo.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "echo.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "echo.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "echo.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: nginx image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} ports: - name: http containerPort: 80 livenessProbe: httpGet: path: {{ .Values.nginx.healthCheck }} port: http readinessProbe: httpGet: path: {{ .Values.nginx.healthCheck }} port: http env: - name: BACKEND_HOST value: {{ .Values.nginx.backendHost | quote }} - name: echo image: "{{ .Values.echo.image.repository }}:{{ .Values.echo.image.tag }}" imagePullPolicy: {{ .Values.echo.image.pullPolicy }} ports: - containerPort: {{ .Values.echo.httpPort }} env: - name: HTTP_PORT value: {{ .Values.echo.httpPort | quote }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }}パラメータは
values.yaml
で指定します。生成されたvalues.yaml
の中身は次のようになっています。values.yaml# Default values for echo. # This is a YAML-formatted file. # Declare variables to be passed into your templates. replicaCount: 1 image: repository: nginx pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. tag: "" imagePullSecrets: [] nameOverride: "" fullnameOverride: "" serviceAccount: # Specifies whether a service account should be created create: true # Annotations to add to the service account annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" podAnnotations: {} podSecurityContext: {} # fsGroup: 2000 securityContext: {} # capabilities: # drop: # - ALL # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000 service: type: ClusterIP port: 80 ingress: enabled: false annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: - host: chart-example.local paths: [] tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi autoscaling: enabled: false minReplicas: 1 maxReplicas: 100 targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 nodeSelector: {} tolerations: [] affinity: {}これを次のように書き換えます。
values.yamlreplicaCount: 1 nginx: image: repository: gihyodocker/nginx pullPolicy: Always tag: latest healthCheck: / backendHost: localhost:8080 echo: image: repository: gihyodocker/echo pullPolicy: Always tag: latest httpPort: 8080Serviceも同様に書き換えます。生成されたServiceの中身は次のようになっています。
service.yamlapiVersion: v1 kind: Service metadata: name: {{ include "echo.fullname" . }} labels: {{- include "echo.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "echo.selectorLabels" . | nindent 4 }}これを次のようにします。
service.yamlapiVersion: v1 kind: Service metadata: name: {{ include "echo.fullname" . }} labels: app: {{ include "echo.name" . }} chart: {{ include "echo.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: {{ .Values.service.name }} selector: {{- include "echo.selectorLabels" . | nindent 4 }}
values.yaml
に次の設定を追記します。values.yamlservice: name: nginx type: ClusterIP port: 80次はIngressです。生成されたIngressの中身は次のようになっています。
ingress.yaml{{- if .Values.ingress.enabled -}} {{- $fullName := include "echo.fullname" . -}} {{- $svcPort := .Values.service.port -}} {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1beta1 {{- else -}} apiVersion: extensions/v1beta1 {{- end }} kind: Ingress metadata: name: {{ $fullName }} labels: {{- include "echo.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ . }} backend: serviceName: {{ $fullName }} servicePort: {{ $svcPort }} {{- end }} {{- end }} {{- end }}
metadata.labels
を書き換えます。ingress.yaml{{- if .Values.ingress.enabled -}} {{- $fullName := include "echo.fullname" . -}} {{- $svcPort := .Values.service.port -}} {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1beta1 {{- else -}} apiVersion: extensions/v1beta1 {{- end }} kind: Ingress metadata: name: {{ $fullName }} labels: app: {{ include "echo.name" . }} chart: {{ include "echo.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ . }} backend: serviceName: {{ $fullName }} servicePort: {{ $svcPort }} {{- end }} {{- end }} {{- end }}
values.yaml
に次の設定を追記します。これはvalues.yaml
生成時のデフォルトの値です。values.yamlingress: enabled: false annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: - host: chart-example.local paths: [] tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.localまた、ServiceAccountは今回作成しないので、
serviceAccount.create
はfalse
とします。values.yamlserviceAccount: # Specifies whether a service account should be created create: false # Annotations to add to the service account annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: ""
values.yaml
全体は次のようになります。values.yamlreplicaCount: 1 nginx: image: repository: gihyodocker/nginx pullPolicy: Always tag: latest healthCheck: / backendHost: localhost:8080 echo: image: repository: gihyodocker/echo pullPolicy: Always tag: latest httpPort: 8080 imagePullSecrets: [] nameOverride: "" fullnameOverride: "" serviceAccount: # Specifies whether a service account should be created create: false # Annotations to add to the service account annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" podAnnotations: {} podSecurityContext: {} # fsGroup: 2000 securityContext: {} # capabilities: # drop: # - ALL # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000 service: name: nginx type: ClusterIP port: 80 ingress: enabled: false annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: - host: chart-example.local paths: [] tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. # limits: # cpu: 100m # memory: 128Mi # requests: # cpu: 100m # memory: 128Mi autoscaling: enabled: false minReplicas: 1 maxReplicas: 100 targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 nodeSelector: {} tolerations: [] affinity: {}
Chart.yaml
にパッケージングに必要な情報を記載します。Chart.yamlapiVersion: v2 name: echo description: A Helm chart for Kubernetes type: application version: 0.1.0 appVersion: 0.1.0
helm lint
で文法チェックができます。$ helm lint echo ==> Linting echo [INFO] Chart.yaml: icon is recommended 1 chart(s) linted, 0 chart(s) failedChartをインストールする
作成したChartをインストールします。
helm install --debug --dry-run リリース名 Chart名
コマンドでインストールのdry runを実行できます。dry runでは生成されたマニフェストを確認することができます。$ helm install --debug --dry-run echo echo install.go:159: [debug] Original chart version: "" install.go:176: [debug] CHART PATH: /hogehoge/echo NAME: echo LAST DEPLOYED: Mon Jun 29 16:58:44 2020 NAMESPACE: default STATUS: pending-install REVISION: 1 USER-SUPPLIED VALUES: {} COMPUTED VALUES: affinity: {} autoscaling: enabled: false maxReplicas: 100 minReplicas: 1 targetCPUUtilizationPercentage: 80 echo: httpPort: 8080 image: pullPolicy: Always repository: gihyodocker/echo tag: latest fullnameOverride: "" imagePullSecrets: [] ingress: annotations: {} enabled: false hosts: - host: chart-example.local paths: [] tls: [] nameOverride: "" nginx: backendHost: localhost:8080 healthCheck: / image: pullPolicy: Always repository: gihyodocker/nginx tag: latest nodeSelector: {} podAnnotations: {} podSecurityContext: {} replicaCount: 1 resources: {} securityContext: {} service: name: nginx port: 80 type: ClusterIP serviceAccount: annotations: {} create: false name: "" tolerations: [] HOOKS: --- # Source: echo/templates/tests/test-connection.yaml apiVersion: v1 kind: Pod metadata: name: "echo-test-connection" labels: helm.sh/chart: echo-0.1.0 app.kubernetes.io/name: echo app.kubernetes.io/instance: echo app.kubernetes.io/version: "0.1.0" app.kubernetes.io/managed-by: Helm annotations: "helm.sh/hook": test-success spec: containers: - name: wget image: busybox command: ['wget'] args: ['echo:80'] restartPolicy: Never MANIFEST: --- # Source: echo/templates/service.yaml apiVersion: v1 kind: Service metadata: name: echo labels: app: echo chart: echo-0.1.0 release: echo heritage: Helm spec: type: ClusterIP ports: - port: 80 targetPort: http protocol: TCP name: nginx selector: app.kubernetes.io/name: echo app.kubernetes.io/instance: echo --- # Source: echo/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: echo labels: app: echo chart: echo-0.1.0 release: echo heritage: Helm spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: echo app.kubernetes.io/instance: echo template: metadata: labels: app.kubernetes.io/name: echo app.kubernetes.io/instance: echo spec: serviceAccountName: default securityContext: {} containers: - name: nginx image: "gihyodocker/nginx:latest" imagePullPolicy: Always ports: - name: http containerPort: 80 livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http env: - name: BACKEND_HOST value: "localhost:8080" - name: echo image: "gihyodocker/echo:latest" imagePullPolicy: Always ports: - containerPort: 8080 env: - name: HTTP_PORT value: "8080" resources: {} NOTES: 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=echo,app.kubernetes.io/instance=echo" -o jsonpath="{.items[0].metadata.name}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace default port-forward $POD_NAME 8080:80dry runでパラメータに問題ないか確認できたらインストールします。
$ helm install echo echo NAME: echo LAST DEPLOYED: Mon Jun 29 16:59:48 2020 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=echo,app.kubernetes.io/instance=echo" -o jsonpath="{.items[0].metadata.name}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace default port-forward $POD_NAME 8080:80アプリが立ち上がっていることがわかります。
$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION echo default 1 2020-06-29 16:59:48.534198 +0900 JST deployed echo-0.1.0 0.1.0 $ kubectl get deployment,service,ingress --selector app=echo NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/echo 1/1 1 1 2m10s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/echo ClusterIP 10.111.40.236 <none> 80/TCP 2m10sIngressの使用がfalseになっていたので、これをtrueにして実行してみます。デプロイ済みのアプリは削除します。
$ helm uninstall echoローカルにはL7ロードバランサがないので、
NGINX Ingress Controller
を使用します。インストール方法はこちらを参照してください。$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud/deploy.yaml $ kubectl -n ingress-nginx get service,pod NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/ingress-nginx-controller LoadBalancer 10.98.29.212 localhost 80:32012/TCP,443:30306/TCP 3m46s service/ingress-nginx-controller-admission ClusterIP 10.107.104.62 <none> 443/TCP 3m46s NAME READY STATUS RESTARTS AGE pod/ingress-nginx-admission-create-6plnl 0/1 Completed 0 3m36s pod/ingress-nginx-admission-patch-csb5s 0/1 Completed 0 3m36s pod/ingress-nginx-controller-579fddb54f-d47d7 1/1 Running 0次に、カスタムValueファイルを作成します。
$ vi echo.yaml ingress: enabled: true hosts: - host: "ch06-echo.gihyo.local" paths: ["/"]カスタムValueファイルを使用してアプリをインストールします。
$ helm install -f echo.yaml echo echo NAME: echo LAST DEPLOYED: Mon Jun 29 17:32:19 2020 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: 1. Get the application URL by running these commands: http://ch06-echo.gihyo.local/
curl
でアクセスしてみます。$ curl http://localhost -H 'Host:ch06-echo.gihyo.local' Hello Docker!!これで成功です!
パッケージをつくる
helm package
コマンドでパッケージングできます。$ helm package echo $ ls echo/ echo-0.1.0.tgz echo.yamlおわりに
今回は「Docker/Kubernetes 実践コンテナ開発入門」をHelm v3で行いました。コンテナまわりの技術は進歩が速くて追いかけるのが大変です。
- 投稿日:2020-06-30T23:22:27+09:00
React.jsをDockerで動かした時のメモ
環境
$ cat /etc/linuxmint/info RELEASE=19.3 CODENAME=tricia EDITION="Cinnamon" DESCRIPTION="Linux Mint 19.3 Tricia" DESKTOP=Gnome TOOLKIT=GTK NEW_FEATURES_URL=https://www.linuxmint.com/rel_tricia_cinnamon_whatsnew.php RELEASE_NOTES_URL=https://www.linuxmint.com/rel_tricia_cinnamon.php USER_GUIDE_URL=https://www.linuxmint.com/documentation.php GRUB_TITLE=Linux Mint 19.3 CinnamonDockerのインストール
$ apt update $ apt install docker-ce docker-ce-cli docker-composenode.jsとcreate-react-appのインストール
$ apt install nodejs $ npm install create-react-app yarnサンプルReact.jsアプリを作る
$ create-react-app docker-test $ cd docker-testとりあえず普通に起動してみる。
yarn startブラウザで http://localhost:3000 にアクセスしReact.jsが動作していることを確認。
確認できたらCtrl-Cで落とす。Dockerfileとdocker-compose.ymlを作成
作成したReactアプリのルートディレクトリに以下のような内容で
Dockerfile
とdocker-compose.yml
を作る。DockerfileFROM node:14.4.0-buster-slim WORKDIR /usr/src/app RUN npm install --save prop-types RUN npm install -g create-react-appdocker-composeversion: '3' services: node: build: context: . tty: true environment: - NODE_ENV=production volumes: - ./:/usr/src/app command: sh -c "yarn start" ports: - "3000:3000"上記のような内容だと、作成したReactアプリのルートディレクトリがDocker環境内では
/usr/src/app
にマウントされるため、 WORKDIRを/usr/src/app
に指定してあれば、実行するスクリプトはsh -c "yarn start"
だけとなるため、これをcommand
で指定している。docker-hub nodeを見に行くと、
alpine
buster
stretch
などの名前がバージョンについています。なんのことかと思ったらLinuxディストリビューションですね。
slim
とついているものは、Debianディストリビューションでエクストラパッケージが含まれないものです。いずれにしても今回はDockerでnodeが動けばいいのでnode:14.4.0-buster-slim
を選択しました。
ちなみにDebianの現在のバージョンは buster(10.0) であり、それ以前は以下のようになります。Debian 10 (buster) — 現在の安定版リリース Debian 9 (stretch) — 過去の安定版リリース Debian 8 (jessie) — 過去の安定版リリース Debian 7 (wheezy) — 過去の安定版リリースalpine linuxはパワーユーザー向けの軽量Linuxのようです。軽量だけにイメージが小さく抑えられるかもしれませんが、私はDebianやUbuntuしか使ったこと無いので
alpine
の使用はやめておきました。Dockerイメージをビルド
$ docker-compose buildDockerでReact.jsアプリを実行する
起動
$ docker-compose upブラウザで http://localhost:3000 にアクセスしReact.jsが動作していることを確認。
Ctrl-C
で停止。バックグラウンドで起動
$ docker-compose up -d停止
$ docker-compose downイメージ情報を確認
$ docker images | grep dockertest dockertest_node latest daf22d9465c7 26 minutes ago 194MBおまけ
すべてのDockerイメージを停止
$ docker stop $(docker ps -q)
- 投稿日:2020-06-30T21:25:29+09:00
ラズパイに向けてRustをクロスコンパイル!
想定読者
- Rustが好きだ!Rustが使いたいんだ!でも、ラズパイに向けたクロスコンパイル方法がわからない!
- クロスコンパイルのやり方は知らなくても、どんなのかは知っている
- Dockerにアレルギー反応を起こさない
- アーキテクチャの名前にアナフィラキシー・ショックを起こさない
- ツールチェイン、リンカという言葉に拒絶反応を起こさない
という人向けの、初心者による初心者のための記事です。
(というか私の備忘録です。)環境
開発環境
OS:Ubuntu 20.04 LTS
アーキテクチャ:x86_64実行環境
ハードウェア:RaspberryPi4 typeB 4GB
OS:Raspbian(Version: May 2020)
アーキテクチャ:ARMv7目標
rust-pushordを用いたRust製GUIアプリケーションをRaspberryPi用にクロスコンパイルして、Raspbian上でも動かすこと。
流れ
- rustの公式Dockerイメージを使って開発環境を整える
- コンテナ内に「Hello World」を用意する。
- CargoへARMv7向けにコンパイルするように設定する
- ツールチェインとリンカを用意する
- コンパイルの確認
- rust-pushordの依存ライブラリを用意する(SDL2)
- rust-pushordのexampleをコンパイルする
- RaspberryPi上で実行する
- 喜びの舞を踊る(option)
1. rustの公式Dockerイメージを使って開発環境を整える
Dockerのインストール等は割愛させていただきます。
インストールが終わったら早速、Rustの公式イメージゲットだぜ!
bashdocker container run -v ホスト:コンテナ -it --rm rust:latest /bin/bash(注:もしかしたらsudoがいるかもね。)
将来的なことを考えるとDockerfileを用意したほうがいい気がするけど、ここでは「初心者による初心者のための」ということで作りません。
2. コンテナ内に「Hello World」を用意する。
初めに、コンテナ内で「Hello World」のコードを作り、確認のため普通(開発環境アーキテクチャ向け)に実行します。
bashroot@abcd12345:/# export USER=root root@abcd12345:~# cd root@abcd12345:~# cargo new hello root@abcd12345:~# cd hello root@abcd12345:~/hello# cargo runこれで「Hello World」が帰ってきたらOK。
3. CargoへARMv7向けにコンパイルするように設定する
次に「.cargo/config」を作成しクロスコンパイルをするための設定を記述します。
bashroot@abcd12345:~/hello# mkdir .cargo root@abcd12345:~/hello# cat <<EOF > .cargo/config [target.armv7-unknown-linux-gnueabihf] linker = "arm-linux-gnueabihf-gcc" EOFこれは
「cargoでtargetにarmv7-unknown-linux-gnueabihfが指定されたら、リンカにarm-linux-gnueabihf-gccを使ってね!」
とcargoにお願いするためのものです。4. ツールチェインとリンカを用意する
さて、次に用意しないと行けないのが「ARMv7用の、ツールチェインとリンカ」です。
bash# ツールチェインの追加 rustup target add armv7-unknown-linux-gnueabihf # 備考:用意されているツールチェインは次のコマンドで表示できます # rustup target list # リンカの用意 apt install gcc-arm-linux-gnueabihf5. コンパイルの確認
さぁ、準備ができたので、コンパイルがちゃんと通るか確かめて見ましょう!
bashroot@abcd12345:~/hello# cargo build --target=armv7-unknown-linux-gnueabihf実際にARMv7上で動くかは、RaspbianのDockerイメージを使ってみたり、実機でテストしてください。
6. rust-pushordの依存ライブラリを用意する(SDL2)
CUIだと寂しいのでGUIアプリをコンパイルしてみましょう!
クロスプラットフォームでさくっとできるのにrust-pushordというのがあるので、これを使います。
任意の場所にGitHubからクローンして、exampleをコンパイルしましょう。
bashroot@abcd12345:~# git clone https://github.com/KenSuenobu/rust-pushrod.git root@abcd12345:~# cd rust-pushrod root@abcd12345:~/rust-pushrod# cargo build --target=armv7-unknown-linux-gnueabihf --example timer残念なことに、失敗しますね。。かなしみ。
... "-ldl" "-lutil" = note: /usr/lib/gcc-cross/arm-linux-gnueabihf/8/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2 /usr/lib/gcc-cross/arm-linux-gnueabihf/8/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2_image /usr/lib/gcc-cross/arm-linux-gnueabihf/8/../../../../arm-linux-gnueabihf/bin/ld: cannot find -lSDL2_ttf collect2: error: ld returned 1 exit status error: aborting due to previous error error: could not compile `rust-pushrod`. To learn more, run the command again with --verbose.どうやらrust-pushordに必要なSDL2が足りていないようです。
では「依存関係を解決するためにapt install libsdl2-devすればいいのか」というと、そうは問屋がおろしません。
なぜなら、必要としているSDL2は「ARMv7向け」だからです。
ということで、次のようにしないといけません。
bash# マルチアーキテクチャでarmhfを有効にする dpkg --add-architecture armhf # 本当は、armhf向けのパッケージが必要とする必須の # 基本的なarmhf向けのアプリをインストールする必要があります。 # しかし、Rustの公式Dockerイメージには、もうすでに入ってるのでスキップします。 # apt install crossbuild-essential-armhf # 必要なSDL2のライブラリをインストール apt install libsdl2-dev:armhf libsdl2-image-dev:armhf libsdl2-ttf-dev:armhfこれで依存ライブラリも揃いました。もう一度コンパイルして、うまく行くことを確認してみてください。
bashroot@abcd12345:~/rust-pushrod# cargo build --target=armv7-unknown-linux-gnueabihf --example timer8. RaspberryPi上で実行する
コンパイルしてできたものを共有フォルダに移し、ホスト側に取り出し、RaspberryPiに持っていってください。
そして実行ししっかりと動くか確認してみましょう。
ついに、RustがRaspberryPi上で動くはずです。(画像は明日くらいに載せます。(現在:2020/6/30))
9. 喜びの舞を踊る(option)
♪(((^-^)八(^∇^)))♪
お疲れ様でした。今回、私自身が「Rustで組み込み!初クロスコンパイル!」ということで、情報を漁るも、簡単には見つからなかったため、記事にさせていただきました。(特にライブラリの依存関係を解消するあたり)
誰かの参考になれば幸いです。
- 投稿日:2020-06-30T21:23:33+09:00
Dockerの不要コンテナを一括削除する
最近AWSのEC2(AmazonLinux2)上でDockerを入れて色々と
遊んで勉強しているのですが、
centosのコンテナを作成する → ごにょごにょする → 終わる、mysqlのコンテナを作る → ごにょごにょする → 終わるなどを繰り返し行い、たまにdocker ps
コマンドでコンテナの稼働状況を確認してたんですが、ある時docker ps -a
を実行したところなんじゃこりゃぁ!!となりました。
そこで「Dockerコンテナは終わっても、Dockerの事は嫌いにならないでください!」と
◯田敦子さんが言ったかどうかは定かではないですが、少なくともDockerコンテナは終わってもDockerコンテナは残っているという事を学んだわけです。無駄な前置きが長くなりましたが、この溜まりに溜まったゴミDockerコンテナを削除するための
コマンドの覚書です。awkを利用した不要コンテナの削除
削除前の状態
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9ec50bb2ac79 mysql "docker-entrypoint.s…" 4 days ago Up 2 days 3306/tcp, 33060/tcp MYSQL 20cabc9db638 centos "/bin/bash" 4 days ago Up 2 days 0.0.0.0:9989->8080/tcp centos_container 149e47fc7182 mysql "docker-entrypoint.s…" 4 days ago Exited (0) 4 days ago silly_haibt 6d0b42666db1 mysql "docker-entrypoint.s…" 4 days ago Exited (0) 4 days ago stoic_panini cc75432e283b mysql "docker-entrypoint.s…" 4 days ago Exited (127) 4 days ago serene_merkle e75ea014088a mysql "docker-entrypoint.s…" 4 days ago Exited (0) 4 days ago laughing_allen 7f6d0144717d centos "/bin/bash" 4 days ago Exited (0) 4 days ago zealous_haibt b78d2f0c9b93 centos "/bin/bash" 4 days ago Exited (0) 4 days ago centos_1 a1adb31a4719 mysql "docker-entrypoint.s…" 4 days ago Exited (0) 4 days ago mysql_container b977c75c9dd7 mysql "docker-entrypoint.s…" 4 days ago Exited (1) 4 days ago bold_nobel 6c590752cab8 mysql "docker-entrypoint.s…" 4 days ago Exited (1) 4 days ago gifted_nightingale上記のうち、稼働中の上2つ以外を削除する。
※今回はdocker ps -f status=<ステータス>
で"Exited"のコンテナのみを削除docker ps -f status=exited | awk '{print $1}' | xargs docker rm実行結果
149e47fc7182 6d0b42666db1 cc75432e283b e75ea014088a 7f6d0144717d b78d2f0c9b93 a1adb31a4719 b977c75c9dd7 6c590752cab8 Error: No such container: CONTAINER[ ~]$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9ec50bb2ac79 mysql "docker-entrypoint.s…" 4 days ago Up 2 days 3306/tcp, 33060/tcp MYSQL 20cabc9db638 centos "/bin/bash" 4 days ago Up 2 days 0.0.0.0:9989->8080/tcp centos_container無事不要なコンテナは消えましたが、ヘッダー行の"CONTAINER"で
docker rm
をしようとしてエラーとなってますね。。(ま、いっか)Docker公式ドキュメント方式(-_-) ※こちらおすすめしております!
上記コマンド作成後、Docker公式マニュアルに稼働していないコンテナ削除方法が載ってました。。
上のしょーもないawk方式なんかより先に載せろよと言われるかもしれませんが...実行前状態(もう一度不要なコンテナを増産しました)
docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 109839ada027 centos "/bin/bash" 35 seconds ago Exited (0) 34 seconds ago vibrant_bohr cf2e8c41b2ee centos "/bin/bash" 36 seconds ago Exited (0) 36 seconds ago brave_mestorf fcfdaf35235d centos "/bin/bash" 37 seconds ago Exited (0) 37 seconds ago gallant_faraday bf7b9fa96291 centos "/bin/bash" 38 seconds ago Exited (0) 38 seconds ago zealous_morse 9ec50bb2ac79 mysql "docker-entrypoint.s…" 4 days ago Up 2 days 3306/tcp, 33060/tcp MYSQL 20cabc9db638 centos "/bin/bash" 4 days ago Up 2 days 0.0.0.0:9989->8080/tcp centos_container削除コマンド
コマンド説明(公式マニュアルより)
このコマンドは停止しているコンテナを全て削除します。コマンド docker ps -a -q は終了した全てのコンテナ ID を rm コマンドに渡し、全て削除するものです。実行中のコンテナは削除されません。
docker rm $(docker ps -a -q)実行結果
109839ada027 cf2e8c41b2ee fcfdaf35235d bf7b9fa96291 Error response from daemon: You cannot remove a running container 9ec50bb2ac797f3cfdbe827aa2793ba135f6f29e9fd2c1b179afa09d6be4b5ba. Stop the container before attempting removal or force remove Error response from daemon: You cannot remove a running container 20cabc9db6380304c3c0c3b8a14f04e0bd60fd58c810ee4c02d6539fd5b4f3f3. Stop the container before attempting removal or force remove実行中のコンテナは削除しようとしてエラーとなります。
試してはいませんが、docker rm -f $(docker ps -a -q)
とするとコンテナ皆殺しの刑に処す!
となるはずですし、ここまで読んだ殆どの方はお気づきかと思いますが、上記awk方式で実施した
内容も下記コマンドで実現できます...docker rm $(docker ps -f status=exited -q)それでは、お後がよろしいようで。。。テケテン
- 投稿日:2020-06-30T20:27:10+09:00
EC2(AmazonLinux2)にJenkinsを構築する
EC2 に Jenkins を構築したのでその作業のメモです。
鍵に権限を付与します。
$ chmod 600 KEY_NAME.pemログインします。
$ ssh -i KEY_NAME.pem ec2-user@{IP_ADDRES}アップデートします。
$ sudo su - $ yum -y update $ yum -y upgradeNginx をインストールします。(必要な場合)
$ amazon-linux-extras install nginx1.12 $ yum -y install nginx $ systemctl enable nginx.service $ systemctl start nginx.service $ systemctl status nginx.serviceDocker をインストールします。
$ yum -y install docker $ systemctl enable docker.service $ systemctl start docker.serviceマウントするディレクトリを作成します。
$ mkdir /home/jenkins_home $ chown -R 1000:1000 /home/jenkins_homeJenkins を起動します。
docker run \ --name jenkins \ --detach \ --volume /home/jenkins_home:/var/jenkins_home \ --publish 8080:8080 \ --publish 50000:50000 \ jenkins/jenkins:ltsNginx の設定を追加します。(必要な場合)
nginx.confuser root; worker_processes 4; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; }jenkins.confserver { listen 80 default_server; listen [::]:80 default_server; server_name _; location / { proxy_set_header Host $http_host; proxy_pass http://localhost:8080; } }Nginxを再起動します。
$ systemctl restart nginx.service
参考
- 投稿日:2020-06-30T19:43:43+09:00
Dockerでローカル開発環境のhttps化
はじめに
ローカル開発環境の https 化
https://blog.jxck.io/entries/2020-06-29/https-for-localhost.html
この記事を読ませて頂き、自分の環境ではこうやってるよというのをシェアしようと思いました。
Let's Encryptによる証明書ではなくて、自己署名証明書を使う方法になります。
また、記事で指摘されているlocalhostの特殊性を無視して粗があっても手早くhttpsで開発したい人向けですhttps化の動機
localhostとの通信を暗号化しても無意味な気がしますが、そうせざるをえない時もあります
- mixed contentでリソースがブロックされる
- FacebookのOAuthで設定するredirect URIはhttpsでないと動かない
- Geolocation APIで位置情報を取得する時にhttpsでないと動かない
そのような時のためにlocalhostをhttpsで使う手順があると便利です。
ngrok
などもありますが、手元で完結するタイプの方法になります。steps
https-portal
imageを使うこちらのイメージを使わせて頂きます。
https://github.com/SteveLTN/https-portaldocker-compose.ymlversion: "3" services: https-portal3000: image: steveltn/https-portal:1 ports: - '3443:443' environment: STAGE: local DOMAINS: 'localhost -> http://host.docker.internal:3000'このコンテナは、
https://localhost:3443
でリクエストを受けてhttp://localhost:3000
にフォワーディングしてくれる設定となります。
(localhost:3000
で開発中のアプリケーションが動いていることを想定)
STAGE=local
この環境変数を渡すとコンテナの中で自己署名証明書を作ってくれます(便利!)
Docker Desktop for Macを使っている場合はこれでOK。他の環境ではextra_hosts
の設定が必要でしょう。
例:docker-compose.ymlversion: "3" services: https-portal3000: image: steveltn/https-portal:1 ports: - '3443:443' environment: STAGE: local DOMAINS: 'localhost -> http://host.docker.internal:3000' extra_hosts: - "host.docker.internal:172.17.0.1"Dockerでアプリケーションを動かしている場合は
host.docker.internal
でなくて直接コンテナに繋いでも動くと思います。Chromeで警告を無視する
これをEnabledにすると localhost に限り自己署名証明書であっても警告が出なくなります。
chrome://flags/#allow-insecure-localhostセキュリティを下げる設定ですが対象がlocalhostのみなので許容します。
以上で https://localhost:3443/ へhttpsアクセスできます。
- 投稿日:2020-06-30T17:43:08+09:00
cx_Oracle が使える Docker イメージ
Python アプリから Oracle データベースに繋ぐ必要があり Python クライアントを入れる Dockerfile を書いたのだが、やたら苦労したので備忘録として書いておく。
DockerfileFROM python:3.7 RUN pip install cx_Oracle # Install Oracle Client ENV ORACLE_HOME=/opt/oracle ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib RUN apt-get update && apt-get install -y libaio1 && rm -rf /var/lib/apt/lists/* \ && wget -q https://download.oracle.com/otn_software/linux/instantclient/19600/instantclient-basic-linux.x64-19.6.0.0.0dbru.zip \ && unzip instantclient-*.zip \ && mkdir -p $ORACLE_HOME \ && mv instantclient_19_6 $ORACLE_HOME/lib \ && rm -f instantclient-*.zipcx_Oracle 8 Initialization — cx_Oracle 8.0.0 documentation
公式ドキュメントによると、cx_Oracle を利用するためには Oracle Client ライブラリがインストールされている必要があり、cx_Oracle は次の順序でライブラリの読み込みを試行する。
cx_Oracle.init_oracle_client(lib_dir="...")
で指定したパス- OS のライブラリパス (
$LD_LIBRARY_PATH
)$ORACLE_HOME/lib
このうち
1.
と3.
の方法で試したときに、他のライブラリは読み込めるのにlibnnz19.so
だけ「そんなファイルは無い」などとエラーが出て大変困った。
原因はいまだにわからない。
2.
の方法を試したところ問題なく読み込まれた。
- 投稿日:2020-06-30T15:23:52+09:00
Dockerで作ったDjangoアプリケーションをHerokuにデプロイする
はじめに
Container Registryを使ってHerokuにデプロイする記事が、Railsが多い一方でDjangoではそんなにヒットしなかったので、アウトプットがてら書いていきます。まだ勉強中の身なので、もし間違えていたら指摘お願いします。
Heroku CLIをダウンロード
始めにこのリンクからHeroku CLIをダウンロードします。僕はwindows 10を使っているので、64ビットインストーラを使ってダウンロードしました。
他のOSを使っている人でも、やり方は詳しく(英語で)書かれているので、その通りにダウンロードしてください。Heroku上でアプリを作成、アドオン等設定
Herokuにログインしてアプリを作成し、OverviewのところからHeroku PostgresをAdd-onsとして設定します。このとき、Hobby Devを選択することで無料で使えるっぽい。(ただし遅いとかなんとか…)
Herokuではvolumeが使えないため、データの永続化をするためにこのようにDBを利用しています。requirement.txtに追記
requirement.txtgunicorn dj-database-urlsetting.pyに追記
以下のようにsetting.pyの下の方にstatic_rootとデータベースの設定を書いていきます。
STATIC_ROOTを設定することで本番環境でも静的ファイルを扱えるようになります。setting.pyDEBUG = False ALLOWED_HOSTS = ['自分のドメイン'] (略) STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') (略) import dj_database_url DATABASES['default'] = dj_database_url.config(conn_max_age=600, ssl_require=True)Procfileを用意
Dockerfileと同じディレクトリにProcfileという名前のファイルを準備し、以下のように記述します。
Procfileweb: gunicorn [プロジェクト名].wsgiプロジェクト名はご自身のプロジェクト名に置き換えてください。
Dockerfileに追記
以下のようにDockerfileの一番下に追記します。
DockerfileCMD gunicorn --bind 0.0.0.0:$PORT [プロジェクト名].wsgiこちらも同様にプロジェクト名を置き換えてください。
コマンドを実行
プロジェクトがあるディレクトリ上でコマンドを実行していきます。
$ heroku login $ heroku container:login $ heroku container:push web -a [アプリ名] $ heroku container:release web -a [アプリ名] $ heroku open -a [アプリ名]No webprocess runningエラーが出たら以下のコマンドを実行しましょう。
$ heroku ps:scale web=1 -a [アプリ名]コンテナとHeroku Postgresを結び付ける
$ heroku addons:attach [postgresコンテナ名] -a [アプリ名]postgresコンテナ名はHerokuのアプリ管理サイトのOverviewのInstalled add-onsから確認することができます。また、最初以外にもマイグレーションが失敗するときなどにこのコマンドを再度実行し、再び結び付け直すことで上手くいくようになることがあります。
マイグレーションの実行
以下のコマンドでマイグレーションを実行します。
$ heroku run python manage.py migrate -a [アプリ名]collectstaticの実行
collectstaticを実行します。whitenoiseを使うとここら辺が省略できるらしい…?
$ heroku run python manage.py collectstatic -a [アプリ名]おわりに
とりあえずDockerを利用してデプロイは出来たものの、なぜかdjango-herokuを使うと500エラーが出たり、静的ファイルが適用出来ていなかったりするので、もうちょっと触りながら勉強します。
- 投稿日:2020-06-30T14:53:39+09:00
Goの実行環境をDockerで構築しようと際に発生した「no Go files in /go/src/github.com/go-playground/localesエラー」を解決
はじめに
root@xxxxxxxxxxxx:/go# go get github.com/go-playground/localesいつものようにパッケージをインストールしていたら次のエラーが発生。
can't load package: package github.com/go-playground/locales: no Go files in /go/src/github.com/go-playground/localesここのlocalesフォルダの中に何もファイルなんてねーよ!って言われてるんですね。そんなこと言われても。。。
解決方法
go getをしても同じエラーが吐かれるだけだったので、試しにlocalesフォルダを削除して、再度以下コマンドを叩いてみました。
root@xxxxxxxxxxxx:/go# go get github.com/go-playground/localesすると2分程度でパッケージのインストールが終わりました。
入った!
無事コンパイルが終了し、GoをDocker環境で構築することができました。Docker便利です。
- 投稿日:2020-06-30T14:25:28+09:00
Docker基本コマンド
はじめに
Dockerについての記事は沢山ありますが
自分用の忘備録です。
覚えたコマンドは随時追加コンテナの起動/再起動
docker start [コンテナ識別子]
コンテナの停止
docker stop [コンテナ識別子]
コンテナの削除
docker rm [コンテナ識別子]
コンテナを作って中に入る
docker run -it [イメージ名] bin/sh
コンテナの名前を変えるコマンド
docker rename [古いコンテナ名][新しいコンテナ名]
Docker-compose build
docker-compose buildimageを作成・Pullするコマンド
docker-compose run
docker-compose run (サービス名) (コマンド) example: docker-compose run --rm app bashコンテナを起動して、コマンドを実行することができる。
-rm を追加することによって、コマンドを実行した後に、
コンテナを削除します。docker-compose up する前などに、一時的にコンテナ内に入り
コマンドを実行したい時などに便利です。docker-compose exec
docker-compose exec (サービス名) (コマンド名) example: docker-compose exec app bashこれは、稼働中のコンテナに入りコマンドを実行するための物です。
docker-compose run
docker-compose upこのコマンドを実行すると、
docker-compose.ymlで記述した通りにコンテナを作成し、
コンテナ同士を繋げて起動をします。-d をオプションとしてつけることで、バックグラウンドでコンテナを起動します。
- 投稿日:2020-06-30T10:48:19+09:00
GithubActionsでのDocker BuildでCacheを効かせる方法メモ
はじめに
GithubActionsでDockerのコンテナをBuildするとデフォルトだとLayerのCache?がされないため、毎回Dockerfileの先頭から実行することになります(何も工夫をしないと)。
「LocalでBuildするときは(Cacheが効いて)速いんだけど、GithubActionsだと遅い」というのは、Buildに時間がかかる場合結構しんどいです(Twitterなどが捗ってしまう)。色々方法があるようですが、Dockerのマルチステージビルドを使っていないなら、割と簡単にCacheを効かせられるようなので、そのメモです(主に自分用)。
Docker BuildでCacheを効かせる方法
ポイントは docker buildコマンドの
--cache-from
と--build-arg BUILDKIT_INLINE_CACHE=1
になります。また、このOptionを指定するには、BuildKitを有効にしないといけないので、環境変数DOCKER_BUILDKIT=1
を指定しておくことが必要になります(buildx でも良いようですが)。上記リンクのドキュメントにも書いてありますが、それぞれ次のような意味になります。
--cache-from
: CacheSourceを指定するコマンドで、外部リポジトリを指定すると適宜アクセスしてくれるようです。--build-arg BUILDKIT_INLINE_CACHE=1
: Build時にMeta情報を埋め込み、--cache-from
で使えるようにします。これをリポジトリにPushしておけば、前回Build時のLayerを再利用できるようになります。- 環境変数
DOCKER_BUILDKIT=1
: こう設定しておくとBuildKitが有効になるようです。GithubActionsでも使えるようです。サンプル
例えば、AWSのECRにPushする場合は、以下のように書くことができます。
aws.ymlon: push: tags: release-* # トリガーは `release-****` というTagがPushされたとき name: Push to Amazon ECR jobs: push: name: BuildAndPush runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR id: build-image env: DOCKER_BUILDKIT: 1 # これ大事 ECR_REGISTRY: 999999999999.dkr.ecr.ap-northeast-1.amazonaws.com # TODO: 要変更 ECR_REPOSITORY: my-awesome-container # TODO: 要変更 IMAGE_TAG: ${{ github.sha }} run: | # latest と github.sha でタグをふる場合 docker build \ --cache-from=$ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg BUILDKIT_INLINE_CACHE=1 \ -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -t $ECR_REGISTRY/$ECR_REPOSITORY:latest . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"さいごに
actions/cache@v2
とか使うともっと良い(速い)のかもしれないですが、使わなくてもできるんだな、と思ったのでメモしておきます。
- 投稿日:2020-06-30T09:10:31+09:00
[mysql,docker,ESET]Lost connection to MySQL server at 'reading initial communication packet', system error: 0の解決例
どんなエラー?
接続者がデータベースに対して権限を持っていない状態で接続を試みると出るエラーのようです
何をしたら出た?
docker-compose up
で予め用意したdocker-compose.ymlに沿って正しく立ち上げで、ローカルでの接続で権限をいじった記憶もないのに出現しました試行錯誤
この記事を見て関係ありそうな部分をすべて試すも空振りしました。
原因
会社のPCということもありセキュリティに厳しくしているようで
ESET Endpoint Security
というファイアウォールをMacのデフォルト以外に取り入れており、そのセキュリティが、データベースへの直接の接続はローカルであっても妨げているようでした。(それだけ保存されたデータは取り扱い注意であるのだとわかりますね。)解決方法
ESETのパーソナルファイアーウォールをオフにすることで接続が可能になります
(手順は、画面左にある
保護の状態
をクリック→画面中央にあるパーソナルファイアーウォール
をクリック→画面中央のパーソナルファイアーウォール
のオンオフスイッチをクリックしてオフにする)いくら取り扱い注意とはいえ、ローカルでSQL文の挙動の実験などをする必要がある場合がありますので、必要に応じて操作できる・できないの状態を変更できる方法を覚えておけるといいと思います。
- 投稿日:2020-06-30T08:52:56+09:00
docker-composeでNode.js(Express)とPostgresSQLを連携したサービスを作る勉強会
概要
先日行われた勉強会にて簡易ではありますが表題のサービスを作りました。Dockerの勉強の延長で取り組みました。この記事ではそのときの勉強会の内容を記載します。
どんなモノをつくるのか?
- チャットのようなアプリケーション
- docker-composeによるデータベースとの連携
- Node.js(Express)でフロントエンド&バックエンド
- データベースはPostgresSQL
完成したソースコード
https://gitlab.com/tamoco-mocomoco/study-docker-compose
参考にさせていただいた記事
https://qiita.com/ryo-ohnishi/items/b54e649b14b51694ef77
作ったモノの説明
docker-compose
サービス:app
app: build: context: ./ # Dockerfile保存場所 depends_on: - database image: n-app # イメージ名 container_name: n-app # コンテナ名 ports: # ポート接続 - 3000:5000 environment: PORT: 5000 DB_USER: postgres DB_HOST: database DB_PORT: 5432 DB_NAME: test_db
app
はNode.js(Express)
がメインとなるサービスになります。ここでは学習のために敢えてデフォルトのポートを5000
に変更しています。これは参考にさせていただいたExpressの環境変数でPORT
が使用されており、それをdocker-compose
で5000
に上書きしています。コンテナ内の5000
をホスト側の3000
に転送しています。ちなみにExpress
で環境変数が使用されている箇所はapp/bin/
のwww
というファイルで確認できます。app/bin/www
#!/usr/bin/env node /** * Module dependencies. */ var app = require('../app'); var debug = require('debug')('myapp:server'); var http = require('http'); /** * Get port from environment and store in Express. */ var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); ~以下省略~サービス:database
database: image: postgres:12.3 volumes: - ./init-sql:/docker-entrypoint-initdb.d:ro environment: POSTGRES_DB: test_db TZ: "Asia/Tokyo" POSTGRES_HOST_AUTH_METHOD: trust
database
はPostgresSQL
を使用しています。docker-compose
で環境変数であるPOSTGRES_DB
をtest_db
に設定することで自動的にtest_db
という名前のデータベースが作成されます。
volumes
の設定はinit-db
というフォルダにSQLファイルを格納しておくことで、SQLファイルに書かれているクエリを自動的に実行してくれます。
初期スキーマの設定(テーブル構造の作成)やダミーデータなどを挿入するときに利用します。実行順序はファイル名の昇順で実行されるので、ファイル名に番号などをつけるとよさそうです。
今回、作成したデータベースには
postgres
というユーザーが作成されますが、パスワードが設定されていません。そのためPOSTGRES_HOST_AUTH_METHOD
をtrust
に設定する必要があります。あと、
TZ
でタイムゾーンを設定しています。タイムゾーンの変更については以下のクエリでも可能です。ALTER DATABASE test_db SET timezone TO 'Asia/Tokyo'; SELECT pg_reload_conf();
init-db
にクエリをファイルとして格納しておけばタイムゾーンも変更可能です。ただし、どこでタイムゾーンが設定されているか後追いしづらいので、環境変数で設定できるなら環境変数を使った方が良いと思います。Dockerfile:Node.js(Express)
今回のアプリケーションの部分(サービス:app)は
app
フォルダ配下
にExpress
のソースが格納されています。それらのソースを元にDockerfile
でコンテナを作成します。Dockerfile
# ベースイメージを指定 FROM node:10.12 # 環境変数設定 ENV NODE_ENV="development" # 作業ディレクトリ作成&設定 WORKDIR /src COPY ./app /src RUN npm install CMD npm run startここで重要なのでコンテナの中に
src
というフォルダを作成し、そこにホスト(自分の端末)のapp
フォルダの中身をコピーします。src
はWORKDIR
に設定されているのでsrc
でコピーしたソースファイルを元にnpm install
などを実行できます。今回は
Dockerfile
でソースをコピーしましたがdocker-compose
でも同じようなことが可能です。Node.js(Express)
ここは別記事にしてもよいくらい説明する箇所が多いので抜粋して記載します。今回の
Express
のソースではapp/routes
フォルダのファイルでエンドポイントを設定して、表示内容をapp/views
フォルダのテンプレートのファイルに渡してコンテンツを表示する流れになっています。処理の流れ app/routes → app/viewslocalhost:3000 にアクセス
app/routes/index.js ↓ app/views/index.ejs
index.js
ではPostgresSQL
のchat
テーブルをセレクトした結果をindex.ejs
に渡しています。localhost:3000/insert?msg=SEND_TEST
app/routes/insert.js ↓ app/views/index.ejs
insert.js
ではURLパラメーターでmsg
を受け取ることができます。msg
の値をchat
テーブルにインサートします。もしパラメーターがない場合は「ふにょふにょ」という値がインサートされます。表示する内容はindex.ejsと同じですがインサートされた後のテーブルの情報を表示します。
app/routes
のファイルとapp/views
のファイルは必ず1対1にする必要はなく、同じテンプレートでも渡す内容で表示内容を切り替えるなどの制御も可能だったりします。データベース(PostgresSQL)への接続:db_client.js
module.exports = { pg_client: function () { const { Client } = require('pg') const client = new Client({ user: process.env.DB_USER, host: process.env.DB_HOST, port: process.env.DB_PORT, database: process.env.DB_NAME }) return client }, pupupu: function () { console.log('pupupupu') } };
db_client.js
でデータベースの接続処理を共通で使用できるようにしています。PostgresSQL
に必要なユーザー情報などはdocker-compose
から環境変数で受け取りそれを元に接続を行います。総評
ある程度コンパクトにまとまっており、構成もそこまで複雑ではないかなと思いました。ただ開発するときに少し難があり
Express
は個別でnpm install
などでpackage.json
などを更新する必要があります。これはdocker-compose build
はapp
フォルダをコピーしているので、アプリのnode_module
などは手動で更新しないとコンテナで動作してくれません。逆にnpm start
などで開発すればよい話になりますが、データベースとの接続ができないので困ったところです。
しかしながら、今回の勉強会ではいろいろとdocker-compose
について理解を深めることができたのでよかったかなと思います。
- 投稿日:2020-06-30T08:36:32+09:00
コンテナイメージサイズ削減手法の効果
サービスインにかかる時間短縮等のため、コンテナイメージサイズはできるだけ小さくする必要があります。Dockerではイメージレイヤ数削減が、コンテナイメージサイズ削減の常套手段です。
しかし、マルチステージビルドが利用可能になったこともあり、コンテナイメージサイズ削減の手法ごとの効果を改めて調べてみました。
対象
- Dockerの概要は知っている人
- コンテナイメージサイズ削減手法の効果が知りたい人
サンプル
以下の条件でコンテナイメージサイズを比較します。
- ベースイメージ:nginx
- トップページ:ngixのLicenseページ
Debian
比較元となるコンテナイメージです。以下のDockerfileを使用します。
FROM nginx:latest WORKDIR /usr/share/nginx/html RUN apt-get update RUN apt-get upgrade -y RUN apt-get install -y wget RUN wget https://nginx.org/LICENSE -O index.htmlAlpine
ディストリビューションの変更によりコンテナイメージサイズ削減を図ります。以下のDockerfileを使用します。
FROM nginx:alpine WORKDIR /usr/share/nginx/html RUN apk update RUN apk upgrade RUN apk add wget RUN wget https://nginx.org/LICENSE -O index.htmlAlpine + &&結合
&&結合によりイメージレイヤ数削減を図ります。以下のDockerfileを使用します。
FROM nginx:alpine WORKDIR /usr/share/nginx/html RUN apk update &&\ apk upgrade &&\ apk add wget &&\ wget https://nginx.org/LICENSE -O index.htmlAlpine + マルチステージビルド
マルチステージビルドの利用により、イメージレイヤ数削減を図ります。マルチステージビルドはDocker ver 17.05以降で利用可能です。以下のDockerfileを使用します。
FROM alpine:latest AS build # .............................. A WORKDIR / RUN apk update RUN apk upgrade RUN apk add wget # ...................................... 1 RUN wget https://nginx.org/LICENSE -O index.html # ...... 2 FROM nginx:alpine # ........................................ B COPY --from=build /index.html /usr/share/nginx/html # ... 3上記のDockerfileの処理を解説します。
A. ビルド用コンテナ
B. 実行コンテナ
- ビルド用コンテナにwegetをインストールする
- ngixのLicenseページをindex.htmlとして保存する
- ビルド用コンテナのindex.htmlを実行コンテナのトップページとする
- ビルド用コンテナはイメージのビルドが終われば削除される
- 実行コンテナはアプリケーションの実行に必要なパッケージのみインストールすればよい
- よって、コンテナイメージサイズの削減が図れる
結果
Debian Alpine Alpine + &&結合 Alpine + マルチステージビルド ディストリビューション Debian Alpine Alpine Alpine メモリ使用量(MB) 152 25.7 25.6 21.3 結論
- ディストリビューションの変更によるコンテナイメージサイズ削減が最も効果が大きかった
- &&結合はイメージレイヤ数が多くないとコンテナイメージサイズ削減の効果を発揮しにくい
- イメージ削減の代償として、可読性も悪くなっている
- 本番用実行コンテナをローカル環境でテストできることが重要で、開発用実行コンテナと本番用実行コンテナとの構成は異なってよいのでは?
- ディストリビューションは共通で、開発時は可読性優先、本番用はコンテナイメージサイズ削減優先とすればよいのでは?
終わりに
コンテナイメージサイズ削減の手法と、手法ごとのコンテナのサイズをまとめました。可読性の問題も絡むため、コンテナイメージサイズ削減の手法はケースバイケースでよいのではないでしょうか。
参考
- 投稿日:2020-06-30T08:09:37+09:00
GitHub * TravisCI * Docker Repository(DockerHub, ECR)の連携
GitHub * TravisCI * Docker Repository の連携
TravisCI を使うことになりちょっと勉強しました。
CircleCIやGitLab CIとそんなに変わらないdockerが使えるCIです。ソースは github にあげました。
ファイル
Djangoの雛形作成
一応、Djangoの雛形作成した手順です。
pyenv, Pipenvはセットアップ済です。$ pipenv --python 3.6 $ pipenv install django==2.2.3 $ pipenv shell (travis_ci) $ django-admin startproject travis_ci (travis_ci) $ cd travis_ci (travis_ci) $ python manage.py migrate.travis.yml
GitHubでタグを打ったら、Docker Hub, ECRにDjango雛形が入ったdockerイメージをプッシュします。
.travis.ymllanguage: python services: - docker env: global: - APP_NAME=sample_travis_ci # awscli - PATH=$HOME/.local/bin:$PATH # Docker Hub - DOCKER_HUB_ACCOUNT=va034600 # ECR - AWS_DEFAULT_REGION=ap-northeast-1 before_install: # ECR - pip install -U pip - pip install awscli script: - echo "script" after_success: - docker --version - docker build -t $APP_NAME:latest -f docker/Dockerfile . - docker images # Docker Hub - echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_ACCOUNT" --password-stdin # Docker Hub public - docker tag $APP_NAME:latest $DOCKER_HUB_ACCOUNT/${APP_NAME}_pub:${TRAVIS_TAG} - docker push $DOCKER_HUB_ACCOUNT/${APP_NAME}_pub:${TRAVIS_TAG} # Docker Hub private - docker tag $APP_NAME:latest $DOCKER_HUB_ACCOUNT/$APP_NAME:${TRAVIS_TAG} - docker push $DOCKER_HUB_ACCOUNT/$APP_NAME:${TRAVIS_TAG} # ECR - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION) - docker tag $APP_NAME:latest $AWS_ECR_ACCOUNT.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$APP_NAME:${TRAVIS_TAG} - docker push $AWS_ECR_ACCOUNT.dkr.ecr.ap-northeast-1.amazonaws.com/$APP_NAME:${TRAVIS_TAG} branches: only: - "/^v?[0-9\\.]+/"Dockerfile
FROM python:3.6.10 WORKDIR /usr/src/app RUN pip install --upgrade pip && pip install pipenv COPY ./Pipfile /usr/src/app/Pipfile COPY ./Pipfile.lock /usr/src/app/Pipfile.lock RUN pipenv install --system COPY . /usr/src/app/ CMD tail -f /dev/null各種サービスの設定
TravisCI
TravisCI の Environment Variablesの設定です。
Docker Repository(DockerHub)
Docker Repository(ECR)
ECR -> リポジトリ -> リポジトリ でrepositoryを作成しました。
起動
最後にDocker Hubに登録したdockerイメージをpullして起動確認しました。
docker-compose.yml
docker-compose.ymlversion: '3' services: api: image: va034600/sample_travis_ci_pub:v2.0.10 working_dir: /usr/src/app/travis_ci command: python manage.py runserver 0.0.0.0:8000 ports: - "8000:8000"$ docker-compose upブラウザで確認できたら終了です。
- 投稿日:2020-06-30T00:03:38+09:00
k8s-the-hard-way in Docker ~ Dockerコンテナ内でkubernetes-the-hard-wayしてみた
概要
kubernetes-the-hard-way を実践しようと思い、自宅環境に手軽に構築したいためにdockerコンテナ上に構築することを考えました。クラスタ構築の勉強方法の一つと捉えていただければ。
事実誤認や別の効率的なやり方等存じていたら教えていただければ幸いです。TL:DR
本稿は、k8sクラスタを手で構築しようとした筆者が詰まったポイントの備忘、もしくは同じくクラスタを構築したいという人がもしかしたら悩むかもしれないポイントの共有を目的としています。
そのため主に詰まったポイントと対処方法、リファレンスについて下記にまとめておきます。
詳細な内容については各章で触れていきます。
Issue Proposal Reference 各コンポーネントバイナリの実行時に”Operation not permitted”のエラー コンテナ実行時にcap_addする DockerCapability LinuxCapability SSL証明書関連で実行権がないと怒られる 署名時に該当のhostnameを追加し直す 本稿参照 /procや/sysへの書き込み権限がないと怒られる write権限を付与して該当のFSをマウント、もしくは再マウント 本稿参照 containerdバイナリ実行時に contained Not Found
依存パッケージ( libseccomp-dev
libc6-compat
)のインストールref1, ref2 コンテナイメージをPullする際に x509: certificate signed by unknown authority
ca-certificates
をインストールproject page servicesリソースへのアクセスができない ルーティングを追加 本稿参照 kube-proxyが write /sys/module/nf_conntrack/parameters/hashsize: operation not supported
--conntrack-max=0
を付加ref kubelet起動時の権限不足 tmpfsをマウント 本稿参照 はじめに
基本的にgcpの機能は使えないということと、dockerの機能で抑制されている部分を変更する必要がありました。
以下、k8s-the-hard-wayのチュートリアル(以降:本家)に沿ってそれぞれ解説していきます。
構築環境は、VirtualBOX上にCPU:1, RAM:4Gi, Disk:32GiのCentos8をminimalインストールした状態です。centos7でも同様の手順で問題なく動くこと実験済みです。環境情報$ cat /etc/redhat-release CentOS Linux release 8.1.1911 (Core) $ grep cpu.cores /proc/cpuinfo cpu cores : 1 $ lsblk | grep disk sda 8:0 0 32G 0 disk $ grep MemTotal /proc/meminfo MemTotal: 3871792 kB $ docker --version Docker version 19.03.8, build afacb8b01-prerequisites
GoogleクラウドのCLI導入およびアカウント登録。
本稿では不要なので割愛。02-client-tools
SSL証明書系の作成ツール(cfssl, cfssljson)およびkubernetes操作用のCLIツール(kubectl)の導入。
証明書一連の作業はその他のコマンドでも可能ですが、cfsslは公開鍵通信用の鍵作成から署名要求(CSR: Certificate Signing Request)、CAによる署名までを一気に行うことが可能なので、導入しておくことが好ましいです、素直にインストラクションに従います。
ちなみにcfsslは鍵のjson形式での出力、cfssljsonで鍵ファイルの保存を行います。$ { curl -O https://storage.googleapis.com/kubernetes-the-hard-way/cfssl/linux/cfssl curl -O https://storage.googleapis.com/kubernetes-the-hard-way/cfssl/linux/cfssljson chmod +x cfssl cfssljson mv cfssl cfssljson /usr/local/bin/ } # 1.3.4 >= であることを確認 $ cfssl version Version: 1.3.4 Revision: dev Runtime: go1.13 $ cfssljson --version Version: 1.3.4 Revision: dev Runtime: go1.13次にkubectlを同様にしてインストール。
こちらはkubernetesのいわば神経系とも言うべきkube-apiサーバに対しリクエストをなげるためのバイナリです。
k8sを扱う際、基本全てkube-api経由で管理することになりますが、例えばクラスタ内のワーカーノードの情報を取得したいといった場合に
curl -X GET --cacert ../ca/ca.pem --cert admin.pem --key admin-key.pem https://balancer:443/api/v1/nodes
とGETリクエストを出し、帰ってきたJSON形式の文字列を処理して・・・といったような面倒くさいことになります。しかしこちらのバイナリを使えば
kubectl get no
と、3単語入れるだけで情報の取得から人間に読みやすい形への整形・出力を勝手にしてくれます。
素のAPI経由での操作が気になればReferenceを読むといいと思います(丸投げ
とてもいい感じにAPIサーバとのやりとりが書かれている記事もありました
→転職したらKubernetesだった件kubectl-install$ { curl -O https://storage.googleapis.com/kubernetes-release/release/v1.15.3/bin/linux/amd64/kubectl chmod +x kubectl sudo mv kubectl /usr/local/bin/ # 1.15.3 >= kubectl version --client }outputClient Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.2", GitCommit:"52c56ce7a8272c798dbc29846288d7cd9fbae032", GitTreeState:"clean", BuildDate:"2020-04-16T11:56:40Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}03-compute-resources
本家ではGCP上にVPCの作成とインスタンスの作成を行なっているが、本稿ではここをDockerコンテナに置き換えます。
なお、ここで作成されるネットワークは以下のような構成になっています。+---------------------------------------------------+ host machine +----------------------------------------------+ | | | | | docker network(k8s) | | +-------------------------------------------+ 172.16.10.0/24 +------------------------------------------+ | | | | | | | | | | | balancer | | | | +-+ 172.16.10.10 +-+ | | | | | | | | | | +------------------+ | | | | | | | | ctl-0 ctl-1 ctl-2 | | | | +-+ 172.16.10.20 +-+ +-+ 172.16.10.21 +-+ +-+ 172.16.10.22 +-+ | | | | | | | | | | | | | | +------------------+ +------------------+ +------------------+ | | | | | | | | worker-0 worker-1 worker-2 | | | | +--+ 172.16.10.30 +--+ +--+ 172.16.10.31 +--+ +--+ 172.16.10.32 +--+ | | | | | | | | | | | | | | | podCIDR | | podCIDR | | podCIDR | | | | | | + 10.200.0.0/24 + | | + 10.200.1.0/24 + | | + 10.200.2.0/24 + | | | | | | | | | | | | | | | | | | | | | | +---------------+ | | +---------------+ | | +---------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | serviceClusterCIDR | | | | | | | +-------------------------------+ 10.100.0.0/16 +----------------------------------+ | | | | | | | | | | +----------------------------------------------------------------------------------+ | | | | | | | | | | | | | | +--------------------+ +--------------------+ +--------------------+ | | | | | | | +-------------------------------------------------------------------------------------------------------+ | +-----------------------------------------------------------------------------------------------------------------+Networking
最初にNetworkingについて説明があるが、ひとまず本チュートリアルの操作に関係のある部分は存在しないので読み流す。
内容的にはkubernetesのネットワーキングの思想は「クラスタ内のpodは一意のIPを持ち、それを通じてノードを問わず互いに認識できる」ことである、というようなことが書いてある。
なお、これもチュートリアルの粋を超えている内容ではあるが、クラスタ内でアクセスコントロールする際はNetworkPolicyリソースを用いる(対応しているPluginをkubelet起動時に指定している必要がある。有名どころはCalicoとかでしょうか)Firewall Rules
今回CentOS8で構築したため、OSインストールした初期状態ではコンテナ間の通信を許可するために下記の設定がホストマシンで必要です。
CentOS7では特に設定は必要ありませんでした。firewall_for_centos8# コンテナからWANへの通信を許可(apk利用のため) $ { nft add rule inet firewalld filter_FWDI_public_allow udp dport 53 accept nft add rule inet firewalld filter_FWDI_public_allow tcp dport { 80,443 } accept # コンテナ内部での通信を許可 nft add rule inet firewalld filter_FWDI_public_allow ip saddr 172.16.10.0/24 ip daddr 172.16.10.0/24 accept }これに関連して格闘した形跡をこちらに記しているので、気が向いたら読んでいただければと思います。
Kubernetes Public IP Address
Load Balancerに紐づけるIPの取得。
本稿ではコンテナでまとめて作るので省略Compute Instances
いよいよ一つの山場。
若干長くなるのと、本家のチュートリアルから外れる内容ではあるので面倒な方はこちらをcloneしていただければそのまま次の04-certificate-authorityに進めます。ここではLoadBalancer、ControlPlane、WorkerNodeの3種類のイメージを作成し、Dockerサービスを作成していきます。
まずDockerイメージをビルドする際無駄にデーモンが重くならないようにディレクトリを分けます。
テスト目的なのでほとんどの実行バイナリをホスト上から共有するようにしています。$ mkdir -p build \ bin/ctl/bin bin/ctl/exec bin/worker/bin bin/worker/exec bin/worker/opt \ lib/balancer lib/ctl lib/worker/cni lib/worker/containerd lib/worker/kubelet lib/worker/kube-proxy \ lib/certs/ca lib/certs/ca_sec lib/certs/ctl lib/certs/worker \ env/common \ cert_configs # こんな感じのディレクトリ構成になります # . # |-- bin # | |-- ctl # | | |-- bin # | | `-- exec # | `-- worker # | |-- bin # | |-- exec # | `-- opt # |-- build # |-- env # | `-- common # |-- lib # | |-- balancer # | |-- certs # | | |-- ca # | | |-- ca_sec # | | |-- ctl # | | `-- worker # | | |-- kubelet # | | `-- kube-proxy # | |-- ctl # | `-- worker # | |-- etccontainerd # | |-- kubelet # | |-- kube-proxy # | `-- varcni # `-- cert_utils以下の内容でDockerfile、entrypoint.sh(サービス起動用のシェルスクリプト)、docker-composeファイルを作成。インスタンス数は本家と同じくLoadBalancer=1, ControlPlane=3, WorkerNode=3で構成しています。
DockerFile、docker-compose.ymlの各Instructionは本家のページを参考ください。build/Dockerfile_ctlFROM alpine:3.11 RUN apk --update --no-cache --no-progress add \ bash \ && rm -rf /var/cache/apk/* #for ETCD runtime RUN mkdir -p /etc/etcd /var/lib/etcd #for kubernetes control #such as api-server, conroller-manager, scheduler RUN mkdir -p /var/lib/kubernetes/ # COPY entrypoint.sh / CMD [ "/bin/bash", "/entrypoint.sh" ]build/Dockerfile_workerFROM alpine:3.11 RUN apk --update --no-cache --no-progress add \ bash \ socat ipset conntrack-tools \ libseccomp-dev libc6-compat \ iptables \ ca-certificates \ && rm -rf /var/cache/apk/* # for provisioning worker node (kubelet, proxy) RUN mkdir -p /etc/cni/net.d \ /opt/cni/bin \ /var/lib/kubelet \ /var/lib/kube-proxy \ /var/lib/kubernetes \ /var/run/kubernetes # # ENV PATH ${C_HOME}/bin COPY entrypoint.sh / CMD [ "/bin/bash", "/entrypoint.sh" ]build/Dockerfile_loadbalancerFROM nginx:alpine RUN printf 'stream {\n\tinclude /etc/nginx/conf.d/*.conf.stream;\n}\n' >> /etc/nginx/nginx.conf見ての通り全てalpineベースで構成している。
重点だけ記しておくと
socat ipset conntrack-tools
はチュートリアル本編に沿って入れる、ネットワーク関連iptables
はkube-proxy動作のために必要だがalpineにデフォルトで入っていないので改めてインストールca-certificates
は証明書インストールで、これがないとcontainerdがイメージをpullする際にx509: certificate signed by unknown authority
となってpullできない- LoadBalancerはnginxのTCPロードバランサー機能を利用します。そのためnginx.confに、別の設定ファイルをhttpブロック外でincludeする行を記載
続けてentrypoint.sh。
単純にkubernetes関連のサービス起動用に作成するシェルスクリプトをスリープを含んでキックしていくだけの代物です。
いろいろ試すためにスクリプトを修正してすぐに反映して、という進め方を想定しているので、openrcなどsysinit系は特に仕込みませんでした(本家からずれてるけど、、)build/entrypoint.sh#!/bin/bash while read svc sleepSec; do [ -f ${svc} ] && { echo " service '$(basename ${svc%.sh})' starting..." /bin/bash ${svc} & sleep ${sleepSec:-5} echo "done." } done<<EOF /exec/ETCD_SERVICE.sh 10 /exec/KUBE-API_SERVICE.sh 20 /exec/KUBE-CONTROLLER-MANAGER_SERVICE.sh /exec/KUBE-SCHEDULER_SERVICE.sh /exec/RBAC_AUTH.sh /exec/CONTAINERD_SERVICE.sh 30 /exec/KUBELET_SERVICE.sh 20 /exec/KUBE-PROXY_SERVICE.sh EOF echo "container Ready" tail -f /dev/null最後にdocker-composeファイル。LinuxCapability について勉強する機会となったので最小限のCapabilityとなるように気をつけましたが、ワーカーノードは結局
--privilrged
をつけないと起動できなかった、、docker-compose.ymlversion: "3.7" x-services: &default-ctl build: context: ./build dockerfile: Dockerfile_ctl image: k8snodes-ctl:0.1 init: true networks: k8s: volumes: - "/sys/fs/cgroup:/sys/fs/cgroup:rw" - "./bin/ctl/exec:/exec:ro" - "./bin/ctl/bin:/usr/local/bin:ro" - "./lib/certs/ctl:/var/lib/kubernetes:ro" - "./lib/certs/ca:/var/lib/ca:ro" - "./lib/certs/ca_sec:/var/lib/ca_sec:ro" cap_drop: - ALL cap_add: - SYS_CHROOT - SETGID - SETUID - CHOWN - NET_RAW - NET_ADMIN - DAC_OVERRIDE dns_search: - k8s environment: - KUBECONFIG=/var/lib/kubernetes/admin.kubeconfig x-services: &default-worker build: context: ./build dockerfile: Dockerfile_worker image: k8snodes-worker:0.1 init: true networks: k8s: volumes: - "/lib/modules:/lib/modules:ro" - "./bin/worker/exec:/exec:ro" - "./bin/worker/bin:/usr/local/bin:ro" - "./bin/worker/opt:/opt/cni/bin:ro" - "./lib/worker/etccontainerd:/etc/containerd:ro" - "./lib/worker/varcni:/var/cni:ro" - "./lib/worker/kubelet:/var/lib/kubelet_config:ro" - "./lib/worker/kube-proxy:/var/lib/kube-proxy_config:ro" - "./lib/certs/worker/kubelet:/var/lib/kubelet_certs:ro" - "./lib/certs/worker/kube-proxy:/var/lib/kube-proxy:ro" - "./lib/certs/ca:/var/lib/ca:ro" - type: tmpfs target: /var/lib/containerd tmpfs: size: 500M privileged: true dns_search: - k8s x-services: &default-balancer build: context: ./build dockerfile: Dockerfile_lb image: nginx_lb:alpine ports: - "10443:443" volumes: - "./lib/balancer:/etc/nginx/conf.d:ro" networks: k8s: cap_drop: - ALL cap_add: - NET_RAW - NET_BIND_SERVICE - CHOWN - SETGID - SETUID services: balancer: << : *default-balancer container_name: balancer hostname: balancer networks: k8s: ipv4_address: 172.16.10.10 ctl-0: << : *default-ctl container_name: ctl-0 hostname: ctl-0 networks: k8s: ipv4_address: 172.16.10.20 ctl-1: << : *default-ctl container_name: ctl-1 hostname: ctl-1 networks: k8s: ipv4_address: 172.16.10.21 ctl-2: << : *default-ctl container_name: ctl-2 hostname: ctl-2 networks: k8s: ipv4_address: 172.16.10.22 worker-0: << : *default-worker container_name: worker-0 hostname: worker-0 networks: k8s: ipv4_address: 172.16.10.30 worker-1: << : *default-worker container_name: worker-1 hostname: worker-1 networks: k8s: ipv4_address: 172.16.10.31 worker-2: << : *default-worker container_name: worker-2 hostname: worker-2 networks: k8s: ipv4_address: 172.16.10.32 networks: k8s: name: k8s ipam: config: - subnet: 172.16.10.0/24 driver_opts: com.docker.network.bridge.name: k8s_brこれも要点だけまとめると
x-
接頭辞はExtensionフィールドであり、記述はできるが解釈されなくなるので、yamlのアンカー・エイリアス構文を用いることができるようになる/sys/fs/cgroup:/sys/fs/cgroup:rw
の権限でのマウントは必須だが、workerではprivilegedオプションをつけているので省略可- workerノードの
/lib/modules:/lib/modules:ro
はkube-proxyに求められるが、無視しても問題ないとは言われるので必須ではない- workerノードのcontainerdのルートディレクトリに
tmpfs
をマウントは、デフォルトのoverlayfsではエラーとなるため必須- dns-searchは互いのノードIPを調べる際に必要となる。<dokcer networkの名前>というドメインに設定されるので、そこに合わせるようにする。
- docker-composeのバージョン3系を用いる場合、
init: true
の部分に関しては3.7>=
となっている。ただしこのオプションを付けなくても特に問題はない。ただしExtensionフィールドは3.4>=
となっているのでそこだけ注意以上です。少々長くなりましたが、これでkubernetesの各コンポーネントを動かす準備ができました。
一応本家ではsshで各インスタンスにアクセスするようになっていますが、ここではdockerなのでプロセスのアタッチで各ノードに入ります。ノードへのアクセス方法$ docker exec -ti <container name> sh04-certificate-authority
kubernetesの各コンポーネントはクラウド基板上で稼働することが前提であり、互いの接続をTLS認証で行います。
このセクションでは自分用PKIを作成して自己署名した証明書を作ります。
まずはCAを作ります。CA$ cd cert_util $ { cat > ca-config.json <<EOF { "signing": { "default": { "expiry": "8760h" }, "profiles": { "kubernetes": { "usages": ["signing", "key encipherment", "server auth", "client auth"], "expiry": "8760h" } } } } EOF cat > ca-csr.json <<EOF { "CN": "Kubernetes", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "JP", "L": "Tokyo", "O": "Kubernetes", "OU": "CA", "ST": "Asia" } ] } EOF # C : Country # L : Locality or City # O : Organization # OU : Organization Unit # ST : State cfssl gencert -initca ca-csr.json | cfssljson -bare ca }CAの公開鍵認証用のペアができます。
ca-key.pem ca.pemここからはkubernetesクラスタのサーバの各コンポーネントと、kubectlを扱うクライアントのための証明書を作成し、このCAによって署名していきます。
各証明書の役割は以下の通りです
- adminユーザ用 (controlプレーン&クライアント)
- kubelet用 (workerノード)
- kube-proxy用 (workerノード)
- Controller-Manager用 (controlプレーン)
- Scheduler用 (controlプレーン)
- Service-Account-Key用 (controlプレーン)
- kubernetes-API-server(兼etcd)用 (controlプレーン)
Service Account Keyに関してはmanaging service accountsに記載ありますが、Contoroller Managerとkube-api-serverコンポーネントで用いられ、各podのプロセスがデフォルトの状態でkube-api-serverにアクセスする権限を持たせるために使われる。ということらしいです。
まずはadminユーザ用。これはcontrolプレーン、及び自分がクラスタと更新する際にkubectlから呼び出して使う用の証明書になります。adminユーザ用# in ./cert_util $ sed ' /"CN"/s/Kubernetes/admin/ /"O"/s/Kubernetes/system:masters/ /"OU"/s/CA/Kubernetes The Hard Way/ ' ca-csr.json | tee admin-csr.json $ cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -profile=kubernetes \ admin-csr.json | cfssljson -bare admin上と同じく、adminと頭についた鍵ペアができます。
*.csr
のファイルはcaで署名される前の署名要求なので、基本鍵ペア以外は不要です。
次にworkerノードの各コンポーネント用の証明書ペアを作成していきます。kubeletではNode Authorizerという認証モードを用いてkubeletからのAPIリクエストを認証するようです。これを用いるためには 「
system:nodes
グループ」に属し、「system:node:<nodeName>
というユーザ名」を持った証明書をkubeletが用いる必要があるようです。公式のドキュメントにもしっかりと記載がありました。
x509公開鍵認証ではCommonName(CN)フィールドの値が「ユーザ名」、Organization(O)フィールドの値が「グループ」となるようなのでここの整合性がちゃんと取れるように気をつけます。ちなみにnodeNameはworkerノード内でhostname
で帰ってくる値と一致している必要がありました。kubelet用# in ./cert_util $ for instance in worker-0 worker-1 worker-2 do sed ' /"CN"/s/admin/system:node:'${instance}'/ /"O"/s/system:masters/system:nodes/ ' admin-csr.json | tee ${instance}-csr.json cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -hostname=${instance} \ -profile=kubernetes \ ${instance}-csr.json | cfssljson -bare ${instance} done
-hostname
で証明書のSAN (Subject Altenative Name)を設定できます。この証明書を使って接続するリモート側のDNS名、もしくはアドレスが、CNもしくはSANに設定されている値に一致しないと証明書エラーとなってしまいます。
実際に試したところkubectlを用いてpodにアタッチする際、kubeletは自身に対してAPIリクエストを出しており、ここで-hostname
を指定しなければkubectl exec
やkubectl logs
を実行しようとする際に証明書エラーとなりました。
本家ではworkerノード自体のInternal・External IPを指定していましたが、特に指定しなくでも(今のところは)動かせたので確認できたケースではDNS名しか要求していない模様。
問題生じたらアップデートしようかと思います。長くなりましたが次にkube-proxy用の証明書。
ここからは特に意識することはなく本家通り粛々と作成していきます。kube-proxy用# in ./cert_util $ sed ' /"CN"/s/admin/system:kube-proxy/ /"O"/s/system:masters/system:node-proxier/ ' admin-csr.json | tee kube-proxy-csr.json $ cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -profile=kubernetes \ kube-proxy-csr.json | cfssljson -bare kube-proxy次にkube-controller-manager用の証明書。一応、ここからはcontrolプレーンの各コンポーネントになります。
kube-controller-manager用# in ./cert_util $ sed ' /"CN"/s/admin/system:kube-controller-manager/ /"O"/s/system:masters/system:kube-controller-manager/ ' admin-csr.json | tee kube-controller-manager-csr.json $ cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -profile=kubernetes \ kube-controller-manager-csr.json | cfssljson -bare kube-controller-managerkube-scheduler用
kube-scheduler用# in ./cert_util $ sed ' /"CN"/s/admin/system:kube-scheduler/ /"O"/s/system:masters/system:kube-scheduler/ ' admin-csr.json | tee kube-scheduler-csr.json $ cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -profile=kubernetes \ kube-scheduler-csr.json | cfssljson -bare kube-schedulerservice-account用
service-account用# in ./cert_util $ sed ' /"CN"/s/admin/service-accounts/ /"O"/s/system:masters/Kubernetes/ ' admin-csr.json | tee service-account-csr.json $ cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -profile=kubernetes \ service-account-csr.json | cfssljson -bare service-account最後にkubernetes-api-server用なのですが、こいつは最低限etcd達とLoad Balancer、あとkubernetesが自動的に作成するサービスリソースのClusterIP(後ほど割り当てるサービスCIDRの、一番最初の序列のIP。CIDRが10.100.0.0/16なら10.100.0.1となる)につながれば動作できたので、そこにSANを限定していきます(本家ではデフォルトで作成されるサービスリソース(kubernetes)のいろいろな形でのドメイン含めた名前が加えられており、そこから外れているが最低限の設定の方が不具合発生時した際に勉強になる部分が多いと感じるので、、)
API_&_etcd用# in ./cert_util $ sed ' /"CN"/s/admin/kubernetes/ /"O"/s/system:masters/Kubernetes/ ' admin-csr.json | tee kubernetes-csr.json $ { LOs='localhost,127.0.0.1' # Control Plane CTLs='172.16.10.20,172.16.10.21,172.16.10.22' # kubernetes services SVC='10.100.0.1,kubernetes,balancer' # 外部のクライアントからアクセスする用の、dockerホスト EXTERNAL='172.16.10.1' cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -hostname=${CTLs},${LOs},${SVC},${EXTERNAL} \ -profile=kubernetes \ kubernetes-csr.json | cfssljson -bare kubernetes }最後にしかるべき場所にこれらの証明書を配置します。
kube-proxy
,kube-controller-manager
,kube-scheduler
, andkubelet
は
次の段階でkubeconfigファイルに含めて用いるので(あと一応、admin
も)、これらは動かしません。# in ./cert_util $ { cp -p ca.pem ../lib/certs/ca mv ca-key.pem ../lib/certs/ca_sec mv service-account*.pem ../lib/certs/ctl mv kubernetes*.pem ../lib/certs/ctl }05-kubernetes-configuration-files
ここではkubernetes APIのクライアントとなる
controller manager
,kubelet
,kube-proxy
, andscheduler
clients and theadmin
user.のための、接続のための認証情報と接続先の情報をkubeconfigファイルという物の中に埋め込んでいきます。
クライアント達の接続先はローカルホスト、及びLoad Balancerとなるので、Load BalancerのIPを本家では取得しています。本稿ではdocker-compose.ymlの中で定めた172.16.10.10
です。$ KUBERNETES_PUBLIC_ADDRESS='balancer'それでは作成していきます。
kubelet用# in ./cert_util $ { for instance in worker-0 worker-1 worker-2; do kubectl config set-cluster kubernetes-the-hard-way \ --certificate-authority=ca.pem \ --embed-certs=true \ --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443 \ --kubeconfig=${instance}.kubeconfig kubectl config set-credentials system:node:${instance} \ --client-certificate=${instance}.pem \ --client-key=${instance}-key.pem \ --embed-certs=true \ --kubeconfig=${instance}.kubeconfig kubectl config set-context default \ --cluster=kubernetes-the-hard-way \ --user=system:node:${instance} \ --kubeconfig=${instance}.kubeconfig kubectl config use-context default --kubeconfig=${instance}.kubeconfig done }kube-proxy用# in ./cert_util $ { kubectl config set-cluster kubernetes-the-hard-way \ --certificate-authority=ca.pem \ --embed-certs=true \ --server=https://${KUBERNETES_PUBLIC_ADDRESS}:443 \ --kubeconfig=kube-proxy.kubeconfig kubectl config set-credentials system:kube-proxy \ --client-certificate=kube-proxy.pem \ --client-key=kube-proxy-key.pem \ --embed-certs=true \ --kubeconfig=kube-proxy.kubeconfig kubectl config set-context default \ --cluster=kubernetes-the-hard-way \ --user=system:kube-proxy \ --kubeconfig=kube-proxy.kubeconfig kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig }kube-controller-manager用# in ./cert_util $ { kubectl config set-cluster kubernetes-the-hard-way \ --certificate-authority=ca.pem \ --embed-certs=true \ --server=https://127.0.0.1:6443 \ --kubeconfig=kube-controller-manager.kubeconfig kubectl config set-credentials system:kube-controller-manager \ --client-certificate=kube-controller-manager.pem \ --client-key=kube-controller-manager-key.pem \ --embed-certs=true \ --kubeconfig=kube-controller-manager.kubeconfig kubectl config set-context default \ --cluster=kubernetes-the-hard-way \ --user=system:kube-controller-manager \ --kubeconfig=kube-controller-manager.kubeconfig kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig }kube-scheduler用# in ./cert_util $ { kubectl config set-cluster kubernetes-the-hard-way \ --certificate-authority=ca.pem \ --embed-certs=true \ --server=https://127.0.0.1:6443 \ --kubeconfig=kube-scheduler.kubeconfig kubectl config set-credentials system:kube-scheduler \ --client-certificate=kube-scheduler.pem \ --client-key=kube-scheduler-key.pem \ --embed-certs=true \ --kubeconfig=kube-scheduler.kubeconfig kubectl config set-context default \ --cluster=kubernetes-the-hard-way \ --user=system:kube-scheduler \ --kubeconfig=kube-scheduler.kubeconfig kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig }adminユーザ用# in ./cert_util $ { kubectl config set-cluster kubernetes-the-hard-way \ --certificate-authority=ca.pem \ --embed-certs=true \ --server=https://127.0.0.1:6443 \ --kubeconfig=admin.kubeconfig kubectl config set-credentials admin \ --client-certificate=admin.pem \ --client-key=admin-key.pem \ --embed-certs=true \ --kubeconfig=admin.kubeconfig kubectl config set-context default \ --cluster=kubernetes-the-hard-way \ --user=admin \ --kubeconfig=admin.kubeconfig kubectl config use-context default --kubeconfig=admin.kubeconfig }最後にこれらもしかるべき場所に配置します。一応、kubelet用の
worker-?.kubeconfig
は本来それぞれのノードにあるべきですが特にそこまで拘らなくてもいいかと思いひとところにまとめています。# in ./cert_util $ { mv admin.kubeconfig kube-scheduler.kubeconfig kube-controller-manager.kubeconfig ../lib/certs/ctl/ mv kube-proxy.kubeconfig ../lib/certs/worker/kube-proxy/ mv worker-?.kubeconfig ../lib/certs/worker/kubelet/ mv worker-*.pem ../lib/certs/worker/kubelet/ }06-data-encryption-keys
ここではKubernetes上で扱われるデータ(APIリソース)の暗号化を行う設定ファイルを作成します。
ここで作成するファイルに関して言えば、APIリソースの中でもsecretsリソースに対して暗号化するようにしています。13-smoke-test
で動作確認します。$ cd .. # in . $ cat | tee lib/certs/ctl/encryption-config.yaml <<EOF kind: EncryptionConfig apiVersion: v1 resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: $(head -c 32 /dev/urandom | base64) - identity: {} EOF07-bootstrapping-etcd
ここではconrolプレーンのデータストアであるetcdを起動させます。
各controlプレーン内で起動させ、3台でクラスタ構成を組みます。
まず、etcd
とetcdctl
のバイナリを入手します# in . $ { curl -LO "https://github.com/etcd-io/etcd/releases/download/v3.4.0/etcd-v3.4.0-linux-amd64.tar.gz" # 最新版は https://github.com/etcd-io/etcd/releases をチェック tar -xf etcd-v3.4.0-linux-amd64.tar.gz mv etcd-v3.4.0-linux-amd64/etcd* bin/ctl/bin/ # controlプレーンで/usr/local/binにマウントする領域。移動したもの以外は削除してよし rm -fr etcd-v3.4.0-linux-amd64* ls -lh bin/ctl/bin/ }etcd起動用のシェルを書きます。
それぞれのホスト名とdockerから割り当てられているIPを動的に入手するようにしています。
それと、証明書類について前章で作成したkubernetes*.pem
が必要になりますが、本編での設定と合わせるためシンボリックリンク貼るようにしています。bin/ctl/exec/ETCD_SERVICE.sh#!/bin/bash ETCD_NAME=$(hostname -s) INTERNAL_IP=$(awk '/'${ETCD_NAME}'/ {print $1}' /etc/hosts) CTLs=$(IFS=',';for i in {0..2};do CTLs[$i]=ctl-${i}=https://172.16.10.2${i}:2380;done;echo "${CTLs[*]}"; ) for link_target in /var/lib/kubernetes/kubernetes.pem /var/lib/kubernetes/kubernetes-key.pem /var/lib/ca/ca.pem; do dest="/etc/etcd/$(basename ${link_target})" [ -L ${dest} ] || ln -s ${link_target} ${dest} done /usr/local/bin/etcd \ --name ${ETCD_NAME} \ --cert-file=/etc/etcd/kubernetes.pem \ --key-file=/etc/etcd/kubernetes-key.pem \ --peer-cert-file=/etc/etcd/kubernetes.pem \ --peer-key-file=/etc/etcd/kubernetes-key.pem \ --trusted-ca-file=/etc/etcd/ca.pem \ --peer-trusted-ca-file=/etc/etcd/ca.pem \ --peer-client-cert-auth \ --client-cert-auth \ --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \ --listen-peer-urls https://${INTERNAL_IP}:2380 \ --listen-client-urls https://${INTERNAL_IP}:2379,https://127.0.0.1:2379 \ --advertise-client-urls https://${INTERNAL_IP}:2379 \ --initial-cluster-token etcd-cluster-0 \ --initial-cluster ${CTLs} \ --initial-cluster-state new \ --log-outputs '/etc/etcd/etcd.log' \ --logger=zap \ --data-dir=/var/lib/etcdオプション名でなんとなく判別できますが、一応要点だけまとめると
- etcdはデフォルトでクラスタ間用の通信にポート番号
2380
を、クライアント用に2379
を用いる(もちろん変更可)- peerでクラスタ間の通信について定義
- clientで外部からのリクエストを受け付ける通信について定義。ローカルを入れてるのはデバッグ時にetcdctlを用いるが、そのデフォルトの向き先がそうなっているため
- clientからの通信の際は
--key
と--cert
で指定した証明書類と適合するものが必要になる--log-outputs
は本家では指定されていない。おそらくsysinitで起動するためと思われるが、ここでは明示的に指定して出力させておく次に動作確認です。
これだけ作成した状態でいったんサービスを起動させます。# in . $ docker-compose up -d ctl-{0..2} Creating network "k8s" with the default driver Creating ctl-2 ... done Creating ctl-0 ... done Creating ctl-1 ... done $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ed27920de960 k8snodes-ctl:0.1 "/bin/bash /entrypoi…" 47 seconds ago Up 44 seconds ctl-1 3e39308fc506 k8snodes-ctl:0.1 "/bin/bash /entrypoi…" 47 seconds ago Up 44 seconds ctl-0 5ca113b3596e k8snodes-ctl:0.1 "/bin/bash /entrypoi…" 47 seconds ago Up 44 seconds ctl-2 $ docker exec -ti ctl-0 /bin/sh -c "ETCDCTL_API=3 etcdctl member list \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/etcd/ca.pem \ --cert=/etc/etcd/kubernetes.pem \ --key=/etc/etcd/kubernetes-key.pem -w table"output+------------------+---------+-------+---------------------------+---------------------------+------------+ | ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER | +------------------+---------+-------+---------------------------+---------------------------+------------+ | 61c4f7534361ad48 | started | ctl-1 | https://172.16.10.21:2380 | https://172.16.10.21:2379 | false | | b167b173b818a05c | started | ctl-0 | https://172.16.10.20:2380 | https://172.16.10.20:2379 | false | | f6d332cd022ef5f8 | started | ctl-2 | https://172.16.10.22:2380 | https://172.16.10.22:2379 | false | +------------------+---------+-------+---------------------------+---------------------------+------------+ちゃんと表示されば成功です。
失敗したら、大抵の場合はdockerホスト側のNetfilterの設定がうまくできていない可能性が高いです。ここまで確認できたらいったん止めます。
$ docker-compose stop
08-bootstrapping-kubernetes-controllers
次にcontrolプレーンの他のコンポーネントを起動していきます。
まずコンポーネント用のバイナリを入手し、共有領域に置いてあげます。# in . $ { curl -L \ -O "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-apiserver" \ -O "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-controller-manager" \ -O "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-scheduler" \ -O "https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubectl" chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl mv kube-apiserver kube-controller-manager kube-scheduler kubectl bin/ctl/bin }続けてそれぞれのサービスを起動するためのスクリプトを書いていきます。
APIサーバ
bin/ctl/exec/KUBE-API_SERVICE.sh#!/bin/bash INTERNAL_IP=$(awk '/'$(hostname -s)'/ {print $1}' /etc/hosts) CTLs=$(IFS=',';for i in {0..2};do CTLs[$i]=https://172.16.10.2${i}:2380;done;echo "${CTLs[*]}"; ) # add route to other node's pods # this config required in "11-pod-network-routes" section for ((i=0;i<3;i++));do ip r add to 10.200.$i.0/24 via 172.16.10.3$i done LOGFILE='/var/log/kube-api.log' /usr/local/bin/kube-apiserver \ --advertise-address=${INTERNAL_IP} \ --allow-privileged=true \ --apiserver-count=3 \ --audit-log-maxage=30 \ --audit-log-maxbackup=3 \ --audit-log-maxsize=100 \ --audit-log-path=/var/log/audit.log \ --authorization-mode=Node,RBAC \ --bind-address=0.0.0.0 \ --client-ca-file=/var/lib/ca/ca.pem \ --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \ --etcd-cafile=/var/lib/ca/ca.pem \ --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \ --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \ --etcd-servers=${CTLs} \ --event-ttl=1h \ --encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \ --kubelet-certificate-authority=/var/lib/ca/ca.pem \ --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \ --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \ --kubelet-https=true \ --runtime-config=api/all=true \ --service-account-key-file=/var/lib/kubernetes/service-account.pem \ --service-cluster-ip-range=10.100.0.0/16 \ --service-node-port-range=30000-32767 \ --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \ --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \ --log-file=${LOGFILE} \ --logtostderr=false \ --v=2
- ここでもログファイルを明示的に指定する部分を追加しています。
- APIリソースの中のserviceリソースが割り当てられるCIDRもここで指定しています。Controller-Managerの起動オプションの中でも指定できますが、こちらで設定した内容の方が優先されました
Controller-Manager
bin/ctl/exec/KUBE-CONTROLLER-MANAGER_SERVICE.sh#!/bin/bash LOGFILE="/var/log/kube-controller-manager.log" /usr/local/bin/kube-controller-manager \ --bind-address=0.0.0.0 \ --cluster-cidr=10.200.0.0/16 \ --cluster-name=kubernetes \ --cluster-signing-cert-file=/var/lib/ca/ca.pem \ --cluster-signing-key-file=/var/lib/ca_sec/ca-key.pem \ --kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \ --leader-elect=true \ --root-ca-file=/var/lib/ca/ca.pem \ --service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \ --use-service-account-credentials=true \ --log-file=${LOGFILE} \ --logtostderr=false \ --v=2kube-Scheduler
ここだけConfigファイルでパラメータを渡します。
基本k8sではConfigファイルを経由してオプション類を渡すことが推奨されています。
APIとController-ManagerだけなぜかConfigファイルを渡すためのオプション等がなくベタ書きという感じでした。コアモジュールだからかわかりませんが、そのうち設定できるようになるのかもしれません。kube-scheduler.yaml# in . $ cat <<EOF | sudo tee lib/certs/ctl/kube-scheduler.yaml apiVersion: kubescheduler.config.k8s.io/v1alpha1 kind: KubeSchedulerConfiguration clientConnection: kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig" leaderElection: leaderElect: true EOFbin/ctl/exec/KUBE-SCHEDULER_SERVICE.sh#!/bin/bash CONFIGFILE='/var/lib/kubernetes/kube-scheduler.yaml' LOGFILE='/var/log/kube-scheduler.log' /usr/local/bin/kube-scheduler \ --config=${CONFIGFILE} \ --log-file=${LOGFILE} \ --logtostderr=false \ --v=2本家ではここで外部のロードバランサーが行うヘルスチェックのため、contarolプレーンの80番ポートに対するリクエストを、kube-api-serverが公開する
https://127.0.0.1:6443/healthz
へと中継させるようにnginxのproxy機能を活用する設定をしています。
が、本稿で用いているロードバランサーはフリー版のnginxで代用しており、製品バージョンでしかActiveヘルスチェック機能は提供されないようでしたので、やるとしたらPassiveですがここまでマネなくてもいいかと思い割愛しました。
最後にkube-api-serverから各workerノードのkubeletにRBACでアクセスするための設定をします。
これはpodのログを取得する際やコマンドを実行する際に用いられます。
後ほどpodは起動するけど上記のような操作ができないといった場合にはこちらのClusterRoleリソースがちゃんと作られているかどうか確認するといいと思います。
こちらはAPIサーバで管理されるリソース作りなので、一度作成すればクラスタ全体で有効になりますが、特に何度適用しても問題はないのでサービス起動時のスクリプトに含めています。
ちなみにこの際にadminユーザのkubeconfigが用いられるようになっています。bin/ctl/exec/RBAC_AUTH.sh#!/bin/bash CONFIGFILE='/var/lib/kubernetes/admin.kubeconfig' cat <<EOF | kubectl apply --kubeconfig ${CONFIGFILE} -f - apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: annotations: rbac.authorization.kubernetes.io/autoupdata: "true" labels: kubernetes.io/bootstrapping: rbac.defaults name: system:kube-apiserver-to-kubelet rules: - apiGroups: - "" resources: - nodes/proxy - nodes/stats - nodes/log - nodes/spec - nodes/metrics verbs: - "*" --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: system:kube-apiserver namespace: "" roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:kube-apiserver-to-kubelet subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: kubernetes ... EOF
- kube-api-serverは
--kubelet-client-certificate
で指定した証明書で持って、kubernetes
ユーザとしてkubeletにアクセスしにいく- そのため作成したClusterRoleリソースを
kubernetes
ユーザに紐づけるClusterRoleBindingリソースを作成しているAPI起動時に上記のオプションで指定した証明書(kubernetes.pem)のCNはkubernetesでした。
それをユーザ名としてリクエストが正当かどうか(resourceに対してverbを行っても良いか)Authorizeするための設定ということですね。
ここまできたところで、Controlプレーンのコンポーネントは全て起動するようになっているはずなので、いったんサービスを起動しヘルスチェックを行います。$ docker-compose up --force-recreate -d ctl-{0..2} $ docker-compose logs -f # 'container Ready'が表示されるまで待ちます。<C-c> $ docker exec -ti ctl-0 sh # in ctl-0 container $ kubectl get cs NAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-0 Healthy {"health":"true"} etcd-2 Healthy {"health":"true"} etcd-1 Healthy {"health":"true"} $ kubectl get clusterRole | grep apiserver-to- system:kube-apiserver-to-kubelet 2020-06-14T05:51:06Z $ kubectl get clusterRoleBinding -o wide| grep apiserver-to- system:kube-apiserver 10m ClusterRole/system:kube-apiserver-to-kubelet kubernetes # 上記のような出力があれば正常に動作している # 'get cs'の結果は帰ってくるけどclusterRole関連が帰ってこない場合はリソースの関係で起動が遅れているので、下記のコマンドで改めて登録し直す $ bash /exec/RBAC_AUTH.sh # 確認できたらホストマシンに戻ります。 $ exitこのセクションの最後にロードバランサーにルールを追加し、正常にバランシングが行われているか確かめています。
本稿ではここをnginx用の設定に置き換えています。# in . $ cat <<EOF | tee lib/balancer/lb.conf.stream upstream k8s_ctl { server ctl-0:6443; server ctl-1:6443; server ctl-2:6443; } server { listen 443; proxy_pass k8s_ctl; } EOF $ docker-compose up -d balancer Creating balancer ... done # dockerのホストマシンの10443番ポートに紐づけられていることを確認 $ docker port balancer # 疎通確認 $ curl --cacert lib/certs/ca/ca.pem https://127.0.0.1:10443/version { "major": "1", "minor": "18", "gitVersion": "v1.18.1", "gitCommit": "7879fc12a63337efff607952a323df90cdc7a335", "gitTreeState": "clean", "buildDate": "2020-04-08T17:30:47Z", "goVersion": "go1.13.9", "compiler": "gc", "platform": "linux/amd64" }証明書類を指定しないアクセスは
system:anonymous
ユーザと解釈されAuthorizationで弾かれますが、/versionは取得できるようです。
上記のような出力が帰ってくれば正常にバランシングできていることになります。長かったですがこれでやっとcontrolプレーンが動くようになりました。
次のセクションでworkerノードを動かせばひとまずクラスタとしては動くようになりますのでもうひと頑張りです。09-bootstrapping-kubernetes-workers
ここではWorkerノードを作成していきます。
最初に依存パッケージを入れていますが、これはDockerfileで入れているため特に操作は不要です。
次にswappingをオフにすることがデフォルトで求められます。
Dockerの場合はホストマシンのシステムファイルを共有するため、ホストマシン(か、--priviledged
しているのでworkerからでもいいですが)にてswapoff
しないとデフォルト設定のkubeletの起動に失敗します。
が、本稿ではそれほど本格的に運用することを目的としているわけではなく、もしswappingがが起きてもそれほど深刻なことにはならないので、swapoff
にせずにkubeletの設定の方を変えます。まず、Workerノード用のバイナリをダウンロードし、展開します。
kubectlも再度ダウンロードしているのですが、workerノードからの使いどころがよくわからなかったので省略しています。# in . $ curl -L \ -O https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.18.0/crictl-v1.18.0-linux-amd64.tar.gz \ -O https://github.com/opencontainers/runc/releases/download/v1.0.0-rc8/runc.amd64 \ -O https://github.com/containernetworking/plugins/releases/download/v0.8.2/cni-plugins-linux-amd64-v0.8.2.tgz \ -O https://github.com/containerd/containerd/releases/download/v1.2.13/containerd-1.2.13.linux-amd64.tar.gz \ -O https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kube-proxy \ -O https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubelet $ { tar -xf containerd-1.2.13.linux-amd64.tar.gz -C bin/worker/ tar -xf cni-plugins-linux-amd64-v0.8.2.tgz -C bin/worker/opt/ tar -xf crictl-v1.18.0-linux-amd64.tar.gz -C bin/worker/bin/ mv runc.amd64 runc chmod +x kube-proxy kubelet runc mv kube-proxy kubelet runc bin/worker/bin rm -f cni-plugins-linux-amd64-v0.8.2.tgz crictl-v1.18.0-linux-amd64.tar.gz containerd-1.2.13.linux-amd64.tar.gz }次にcniの設定を行います。
このチュートリアルではコンテナランタイムとしてcontainerdを直接用いていますが、こいつがpodを作る際にこちらのcniを用いてネットワークデバイスを割り当てます。
kubeletではなく自分の手でcniを使ってネットワークデバイスを作成するといった試みはネットワーク上探すと結構出てきますが、大元の使い方はこちらにありますので、やってみると余計kubeletのありがたみを感じるかもしれません(知らんけど)
設定にあたり、ipam
のhost-local
では、ここで定義したレンジからpodにアドレスを割り当てます。あくまでkubeletが管理しており、kube-api-serverは「こういうpod作ったよ」情報を保持するのみです。
つまり、ここで渡すCIDRが被ってしまうと同じIPを持つpodがたくさんできてしまいアクセスできなくなるので、workerノードごとにCIDRレンジを変更する必要があります。
そのように動的に設定するため、まずは各workerノードコンテナ内の/var/cniに一時的に設定ファイルを起き、containerd起動のスクリプト内で置換して本来の場所に移すように作ります。# in . $ cat <<EOF | sudo tee lib/worker/varcni/10-bridge.conf { "cniVersion": "0.3.1", "name": "bridge", "type": "bridge", "bridge": "cnio0", "isGateway": true, "ipMasq": true, "ipam": { "type": "host-local", "ranges": [ [{"subnet": "\${POD_CIDR}"}] ], "routes": [{"dst": "0.0.0.0/0"}] } } EOF $ cat <<EOF | sudo tee lib/worker/varcni/99-loopback.conf { "cniVersion": "0.3.1", "name": "lo", "type": "loopback" } EOF次にcontainerdを起動するスクリプトを書いていきます。
containerdは外部から設定ファイルを読み込みます。
ファイルにて設定できる項目はcontainerd config default
コマンドで確認できます。$ cat << EOF | tee lib/worker/etccontainerd/config.toml [plugins] [plugins.cri.containerd] snapshotter = "overlayfs" [plugins.cri.containerd.default_runtime] runtime_type = "io.containerd.runtime.v1.linux" runtime_engine = "/usr/local/bin/runc" runtime_root = "" EOFbin/worker/exec/CONTAINERD_SERVICE.sh#!/bin/bash LOGFILE="/var/log/containerd.log" cp -p /var/cni/* /etc/cni/net.d/ POD_CIDR="10.200.${HOSTNAME#worker-}.0/24" CNICONF='/etc/cni/net.d/10-bridge.conf' sed -i 's|${POD_CIDR}|'${POD_CIDR}'|' ${CNICONF} /usr/local/bin/containerd \ --config /etc/containerd/config.toml >>${LOGFILE} 2>&1次にkubeletの起動スクリプト
workerノード内のバイナリは基本設定ファイルを読み込ませる形で起動します。
まずは設定ファイルの中身です。ここでもノードごとに動的に変えたい部分が存在しますので、kubelet起動時に置き換えるようにしています。
本家ではここでもPODCIDRを指定していますが、これはstandaloneモードでのみ用いられるもののようです。今回はクラスタモードで起動するので特に指定する必要はありません。さらに言えばhost-localなcniを用いるのでMasterノードで行うべき設定すらも関係ありません。
それと、各ノードで実行されるpodへの通信を確保するためにルーティングテーブルを作成する必要があります。そのルーティングも動的に実行するように設定しています。
まずは設定ファイルです。# in . $ cat <<EOF | tee lib/worker/kubelet/kubelet-config.yaml kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 authentication: anonymous: enabled: false webhook: enabled: true x509: clientCAFile: "/var/lib/ca/ca.pem" authorization: mode: Webhook clusterDomain: "cluster.local" clusterDNS: - "10.100.0.10" # resolvConf: "/run/systemd/resolve/resolv.conf" runtimeRequestTimeout: "15m" tlsCertFile: "/var/lib/kubelet_certs/\${HOSTNAME}.pem" tlsPrivateKeyFile: "/var/lib/kubelet_certs/\${HOSTNAME}-key.pem" failSwapOn: false EOF起動スクリプト
bin/worker/exec/KUBELET_SERVICE.sh#!/bin/bash CONFIGFILE="/var/lib/kubelet-config.yaml" LOGFILE="/var/log/kubelet.log" NodeNum=${HOSTNAME#worker-} sed -e ' s|${HOSTNAME}|'${HOSTNAME}'|g ' /var/lib/kubelet_config/kubelet-config.yaml > ${CONFIGFILE} # add route to other node's pods # this config required in "11-pod-network-routes" section for ((i=0;i<3;i++));do [ $i -ne ${NodeNum} ] && ip r add to 10.200.$i.0/24 via 172.16.10.3$i done /usr/local/bin/kubelet \ --config=${CONFIGFILE} \ --container-runtime=remote \ --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \ --image-pull-progress-deadline=2m \ --kubeconfig=/var/lib/kubelet_certs/${HOSTNAME}.kubeconfig \ --network-plugin=cni \ --register-node=true \ --logtostderr=false \ --log-file=${LOGFILE} \ --v=2最後にkube-proxyです。
こいつはノードごとのネットワーク設定を、主にiptables(可変だけども)を用いて操作する担当です。
なのでServiceリソースを作った時なんかに各ノードにてiptablesでみてみると、確率でトラフィックを振り分けているServiceの実態が見えたりします。
それと、dockerコンテナの方に--priviledged
をつけているにもかかわらずデフォルトで/proc/sys以下をReadOnlyモードでマウントするらしく、ここをReadWriteでリマウントしてあげないとこれもまたエラーになります。Workerノードは何もかも捧げているといった感じですね。
ここでもCIDRを指定していますが、ここでの意味合いは指定したCIDRレンジ以外へのトラフィックをマスカレードするという設定になるらしいです。# in . cat <<EOF | sudo tee lib/worker/kube-proxy/kube-proxy-config.yaml kind: KubeProxyConfiguration apiVersion: kubeproxy.config.k8s.io/v1alpha1 clientConnection: kubeconfig: "/var/lib/kube-proxy/kube-proxy.kubeconfig" mode: "iptables" clusterCIDR: "10.200.0.0/16" conntrack: maxPerCore: 0 EOFbin/worker/exec/KUBE-PROXY_SERVICE.shCONFIGFILE="/var/lib/kube-proxy_config/kube-proxy-config.yaml" LOGFILE="/var/log/kube-proxy.log" #re-mount the Required File System if that was as ReadOnly mode RFS='/proc/sys' [[ $(awk -v RFS=${RFS} '/^proc/ {if($2==RFS){split($4, mode, ",")}}END{print mode[1]}' /proc/mounts) == 'ro' ]] && \ mount -o remount,rw,bind ${RFS} ${RFS} /usr/local/bin/kube-proxy \ --config=${CONFIGFILE} \ --logtostderr=false \ --log-file=${LOGFILE} \ --v=2以上で k8sクラスタコンポーネントの起動設定は完了です。
Workerも起動し、無事ノードとして認識されるか確かめて次のセクションに移動します。# in . $ docker-compose up -d worker-{0..2} # 'container Ready'が表示されるくらいまで待ちます $ docker exec -it ctl-0 kubectl get no NAME STATUS ROLES AGE VERSION worker-0 NotReady <none> 7s v1.18.1 worker-1 NotReady <none> 7s v1.18.1 worker-2 NotReady <none> 7s v1.18.110-configuring-kubectl
このセクションでは、自分作成したクラスタに対して任意の場所からアクセスできるようにします。
そのためにはkubeconfigという設定ファイルに接続先と認証情報を書き込み、kubectlを打つときにこの設定ファイルを参照するようにしてあげます。
まず接続先はbalancer(つまりdockerホストのネットワークデバイス(172.16.10.1)の10443ポート)になります。# in . $ ( cd cert_util/ kubectl config set-cluster kubernetes-the-hard-way \ --certificate-authority=ca.pem \ --embed-certs=true \ --server=https://172.16.10.1:10443 kubectl config set-credentials admin \ --client-certificate=admin.pem \ --client-key=admin-key.pem \ --embed-certs=true kubectl config set-context kubernetes-the-hard-way \ --cluster=kubernetes-the-hard-way \ --user=admin kubectl config use-context kubernetes-the-hard-way )kubectlが用いるconfigファイルは
1.--kubeconfig
で指定した値
2.$KUBECONFIG
の値
3.${HOME}/.kube/config
の順で参照されますので、特に何もいじっていないこの場合は
${HOME}/.kube/config
にここで設定した値が保存されています。最後に疎通確認です。
# from docker host machine $ kubectl get cs NAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-2 Healthy {"health":"true"} etcd-0 Healthy {"health":"true"} etcd-1 Healthy {"health":"true"}11-pod-network-routes
ここではあるpodから別のworkerノードで動いているpodに対しても疎通ができるようにルーティング設定を行います。具体的にはpodからのトラフィックを、宛先podが動いているworkerノードのethデバイスにルーティングするようにします。
本来GWであるdockerホストにおいて、これように作成されるデバイス用に下記のように設定を行うべきです、が、実は本チュートリアルにおいて、この設定自体は08-bootstrapping-kubernetes-controllers
及び09-bootstrapping-kubernetes-workers
内のkubelet起動スクリプトの中に組み込んでいます ので、pod宛の通信は各ノードが直接ルーティングするようになっています。
これによりdockerコンテナの方をdownさせると自動的に設定自体も削除されるので、個人的にはよりクリーンかなと。
ということでこのセクションも割愛します。一応解説だけしておくと、キモの部分は自分以外のpodへのトラフィックのみルーティングを行うという部分です。
今回workerノードとなっているdockerコンテナは、基本vethデバイスをdokcerホストマシン内に作成されるbridgeデバイスに差し込んで各種通信を行います。そのため各種通信は必ずホストマシンのbridgeデバイスを介して行われます。詳しくは別で記事にしています。
同様のことがworkerとpodの間でも行われます。つまりpodからの通信はworker内に自動で生成されるbridgeデバイスを介して行われるので、もしここで自分の中で動いているpodへのトラフィックを自分のethデバイスにむけてルーティングするとそこでループとなりトラフィックが消失します(最初手探りで構築していたので、途中でトラフィックが消えていたことをコンテナのバグかと思い込みすごくハマりました、、、)。12-dns-addon
ここではpod及びserviceリソースに対する名前解決を行えるようにするaddonをデプロイします。
kubernetesで使用されるDNSは、現在ではkube-dnsというのがレガシーでcorednsがモダンのようです。
本家でも corednsを用いているので素直にその通りにします。
ただ当チュートリアルでは手作り感を出すためにserviceリソース用のCIDRを変更しており、本家で行っているデプロイ用のYAMLをそのまま用いることはできないので少しだけ変更を加えます。
まず、元となるYAMLをダウンロードします。# in . $ curl -O https://storage.googleapis.com/kubernetes-the-hard-way/coredns.yaml # お好みのテキストエディタか下記コマンドで該当箇所修正します。 $ sed -i '/clusterIP/s/32/100/' coredns.yaml $ kubectl apply -f coredns.yaml serviceaccount/coredns created clusterrole.rbac.authorization.k8s.io/system:coredns created clusterrolebinding.rbac.authorization.k8s.io/system:coredns created configmap/coredns created deployment.apps/coredns created service/kube-dns created # ちゃんと作成され、DNSサービスが機能するか確かめます $ kubectl get pods -l k8s-app=kube-dns -n kube-system NAME READY STATUS RESTARTS AGE coredns-589fff4ffc-d8745 1/1 Running 0 21s coredns-589fff4ffc-k6zwl 1/1 Running 0 22s $ kubectl run busy --image=busybox --rm -ti -- sh # もしここでPermissionDeniedになったらkubeapi-to-kubeletのrbacが正常に作られているかcontrolプレーンの中で確認してみてください。 # もしくはとりあえず下記コマンド打ってみてください # docker exec ctl-0 bash /exec/RBAC_AUTH.sh # # APIサーバを起動したときにデフォルトで作成されている'kubernetes'という名前のサービスリソースを名前解決します。 # in pod $ nslookup kubernetes Server: 10.100.0.10 Address: 10.100.0.10:53 Name: kubernetes.default.svc.cluster.local Address: 10.100.0.1 $ exit13-smoke-test
ここではkubernetesリソース関連の各種操作を行なっていきます。
ここに記載の範囲の操作は可能なことは確認できています。secrets
ここでは
06-data-encryption-keys
で実施したsecretsリソースがちゃんと暗号化されていることを確認します。# ctl-0にattach $ docker exec -ti ctl-0 sh $ kubectl create secret generic kubernetes-the-hard-way --from-literal="mykey=mydata" $ etcdctl get \ --cacert=/etc/etcd/ca.pem \ --key=/etc/etcd/kubernetes-key.pem \ --cert=/etc/etcd/kubernetes.pem \ /registry/secrets/default/kubernetes-the-hard-way | hexdump -C 00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret| 00000010 73 2f 64 65 66 61 75 6c 74 2f 6b 75 62 65 72 6e |s/default/kubern| 00000020 65 74 65 73 2d 74 68 65 2d 68 61 72 64 2d 77 61 |etes-the-hard-wa| 00000030 79 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 |y.k8s:enc:aescbc| 00000040 3a 76 31 3a 6b 65 79 31 3a 84 82 28 36 9a 28 ef |:v1:key1:..(6.(.| #---<省略>---aescbcという文字列が、このアルゴリズムがそれ以降文字列の暗号化に使われたということを示すようです。
試しにそれ以外のリソースについて暗号化されていないのか確かめてみます。# still in ctl-0 # 'default' namespaceに関連して保存されているリソースの表示 $ etcdctl get \ --cacert=/etc/etcd/ca.pem \ --key=/etc/etcd/kubernetes-key.pem \ --cert=/etc/etcd/kubernetes.pem "" --prefix --keys-only | grep default # kubernetesサービスリソースの情報確認 $ etcdctl get \ --cacert=/etc/etcd/ca.pem \ --key=/etc/etcd/kubernetes-key.pem \ --cert=/etc/etcd/kubernetes.pem /registry/services/specs/default/kubernetes | hexdump -Coutputs
00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 72 76 69 63 |/registry/servic| 00000010 65 73 2f 73 70 65 63 73 2f 64 65 66 61 75 6c 74 |es/specs/default| 00000020 2f 6b 75 62 65 72 6e 65 74 65 73 0a 6b 38 73 00 |/kubernetes.k8s.| 00000030 0a 0d 0a 02 76 31 12 07 53 65 72 76 69 63 65 12 |....v1..Service.| 00000040 87 04 0a bb 03 0a 0a 6b 75 62 65 72 6e 65 74 65 |.......kubernete| 00000050 73 12 00 1a 07 64 65 66 61 75 6c 74 22 00 2a 24 |s....default".*$| 00000060 35 39 66 35 32 61 62 62 2d 34 61 39 61 2d 34 63 |59f52abb-4a9a-4c| 00000070 30 34 2d 39 64 35 61 2d 35 33 36 30 32 34 32 34 |04-9d5a-53602424| 00000080 36 63 63 38 32 00 38 00 42 08 08 82 e7 9f f7 05 |6cc82.8.B.......| 00000090 10 00 5a 16 0a 09 63 6f 6d 70 6f 6e 65 6e 74 12 |..Z...component.| 000000a0 09 61 70 69 73 65 72 76 65 72 5a 16 0a 08 70 72 |.apiserverZ...pr| 000000b0 6f 76 69 64 65 72 12 0a 6b 75 62 65 72 6e 65 74 |ovider..kubernet| 000000c0 65 73 7a 00 8a 01 b8 02 0a 0e 6b 75 62 65 2d 61 |esz.......kube-a| 000000d0 70 69 73 65 72 76 65 72 12 06 55 70 64 61 74 65 |piserver..Update| 000000e0 1a 02 76 31 22 08 08 82 e7 9f f7 05 10 00 32 08 |..v1".........2.| 000000f0 46 69 65 6c 64 73 56 31 3a 85 02 0a 82 02 7b 22 |FieldsV1:.....{"| 00000100 66 3a 6d 65 74 61 64 61 74 61 22 3a 7b 22 66 3a |f:metadata":{"f:| 00000110 6c 61 62 65 6c 73 22 3a 7b 22 2e 22 3a 7b 7d 2c |labels":{".":{},| 00000120 22 66 3a 63 6f 6d 70 6f 6e 65 6e 74 22 3a 7b 7d |"f:component":{}| 00000130 2c 22 66 3a 70 72 6f 76 69 64 65 72 22 3a 7b 7d |,"f:provider":{}| 00000140 7d 7d 2c 22 66 3a 73 70 65 63 22 3a 7b 22 66 3a |}},"f:spec":{"f:| 00000150 63 6c 75 73 74 65 72 49 50 22 3a 7b 7d 2c 22 66 |clusterIP":{},"f| 00000160 3a 70 6f 72 74 73 22 3a 7b 22 2e 22 3a 7b 7d 2c |:ports":{".":{},| 00000170 22 6b 3a 7b 5c 22 70 6f 72 74 5c 22 3a 34 34 33 |"k:{\"port\":443| 00000180 2c 5c 22 70 72 6f 74 6f 63 6f 6c 5c 22 3a 5c 22 |,\"protocol\":\"| 00000190 54 43 50 5c 22 7d 22 3a 7b 22 2e 22 3a 7b 7d 2c |TCP\"}":{".":{},| 000001a0 22 66 3a 6e 61 6d 65 22 3a 7b 7d 2c 22 66 3a 70 |"f:name":{},"f:p| 000001b0 6f 72 74 22 3a 7b 7d 2c 22 66 3a 70 72 6f 74 6f |ort":{},"f:proto| 000001c0 63 6f 6c 22 3a 7b 7d 2c 22 66 3a 74 61 72 67 65 |col":{},"f:targe| 000001d0 74 50 6f 72 74 22 3a 7b 7d 7d 7d 2c 22 66 3a 73 |tPort":{}}},"f:s| 000001e0 65 73 73 69 6f 6e 41 66 66 69 6e 69 74 79 22 3a |essionAffinity":| 000001f0 7b 7d 2c 22 66 3a 74 79 70 65 22 3a 7b 7d 7d 7d |{},"f:type":{}}}| 00000200 12 43 0a 1a 0a 05 68 74 74 70 73 12 03 54 43 50 |.C....https..TCP| 00000210 18 bb 03 22 07 08 00 10 ab 32 1a 00 28 00 1a 0a |...".....2..(...| 00000220 31 30 2e 31 30 30 2e 30 2e 31 22 09 43 6c 75 73 |10.100.0.1".Clus| 00000230 74 65 72 49 50 3a 04 4e 6f 6e 65 42 00 52 00 5a |terIP:.NoneB.R.Z| 00000240 00 60 00 68 00 1a 02 0a 00 1a 00 22 00 0a |.`.h......."..| 0000024e "情報が平文で保存されていることが確認できます。
deployment
ここではアプリケーションを運用していく上で主に使われることになるdeploymentリソースの作成です。
$ kubectl create deployment nginx --image=nginx $ kubectl get po -w # コンテナがRunningになるまで待ちます。確認できたらCtl-Cで抜けます。Port Forwarding
ここではkubectlを用いるホスト(ここではdockerホスト)に、podのポートへとforwardingを行う操作の確認を行います。
主にデバッグ目的で行われる操作になります。# 上のdeploymentで表示したpodの名前をコピーします。 $ POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}") $ kubectl port-forward $POD_NAME 8080:80 Forwarding from 127.0.0.1:8080 -> 80 Forwarding from [::1]:8080 -> 80 # in new terminal of docker host $ curl --head 127.0.0.1:8080 HTTP/1.1 200 OK Server: nginx/1.19.0 Date: Mon, 15 Jun 2020 10:11:53 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 26 May 2020 15:00:20 GMT Connection: keep-alive ETag: "5ecd2f04-264" Accept-Ranges: bytesLogs
podのログを表示します。デバッグの基本となる、個人的には重要なコマンドです。
先ほどのアクセスログが表示されるはずです。$ kubectl logs $POD_NAME 127.0.0.1 - - [15/Jun/2020:10:11:53 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.61.1" "-"Exec
podの中でコマンドを実行します。
上記のログ同様、デバッグを行う上で結構用います。kubectl exec -ti $POD_NAME -- nginx -vServices
ここでは先ほど作成したpodに、その付加されたタグに該当すればトラフィックをフォワードするというサービスリソースを作成します。
kube-proxyを作成する段でも少し触れましたが、iptabesなどNetfilter操作系のコマンドで実装されているようです。
どのタグがどのpodについて、、とかを考えて作ると割と面倒なのですが、kubectlには一括でpodやdeploymentなどのリソースとサービスを紐づけるためのコマンドがあります。それがexpose
になります。
ここでは完全に外部からアクセスするため、NodePort型のサービスリソースを作成しています。
ClusterIPではあくまでpodへの通信をforwardするため、サービスクラスタのネットワークが見える環境でなければいけませんが(つまり今回の環境においてはホスト上で10.100.0.0/16への通信をどこかのノードへルーティングする必要があります)、NodePortはpodがデプロイされるworkerノードのポートからpodにフォワードするため、何もしなくでもdockerホストからpodへと疎通できるようになります。$ kubectl expose deployment nginx --port 80 --type NodePort $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 162m nginx NodePort 10.100.211.41 <none> 80:31867/TCP 7s $ NODE_PORT=$(kubectl get svc nginx \ --output=jsonpath='{range .spec.ports[0]}{.nodePort}') # 上で表示したportの値となる # どこか適当なworkerノードのポートに疎通確認 $ curl -I 172.16.10.30:${NODE_PORT} HTTP/1.1 200 OK Server: nginx/1.19.0 Date: Mon, 15 Jun 2020 10:47:25 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 26 May 2020 15:00:20 GMT Connection: keep-alive ETag: "5ecd2f04-264" Accept-Ranges: bytes14-cleanup
ここまでに構築した環境を一掃します。
特にカスタマイズしてなければ下記コマンド一発です。# in . $ docker-compose downちなみにまたサービスをupすれば再度クリーンなkubernetesクラスタの実行環境が立ち上がります。
$ docker-compose up
おわりに
以上、docker環境上でhard-wayするための方法と、つまづいたところの共有になります。
実際にやってみようとなっても、ここで行うテストの範囲ならかなり低リソース(再喝しますがdockerホストはCPU: 1コア、RAM: 4GB、ディスク: 32GBです)でも十分動きました。全てフリーの範囲なのでお金の心配もありません。
意外とネットワーク周りの理解に手間取り結構時間かかってしまいましたが、コンテナ技術を理解するための入り口としてとてもいいチュートリアルでした。hard-wayしたいけどパブリッククラウドに手を出すと後片付け忘れそうで不安、サインアップが面倒くさい、自前の環境で済ませたい、コンテナの制限を知りたいなど、自分と似た境遇の方の何かの助けになれば幸いです。