OpenShiftやKubernetes上でJavaを動かす際の注意
OpenShift 全部俺 Advent Calendar 2017
OpenShiftやKubernetes上でリソース制限を設定したコンテナでJavaを動かすとき、デフォルト設定のままだとパフォーマンスが悪くなったり、oom-killerに殺されたりします。これはコンテナのcgroupsの制限をJavaが考慮しないためです。
Javaはデフォルトでホストのメモリの1/4を最大Javaヒープメモリに設定し、Java VM本体、スタック、Metaspaceなどの非Javaヒープ領域を含めると最終的にその倍程度のメモリを利用します。たとえば16GBのマシンだと8GBくらいです。これを4GBなどの制限で動作させるとメモリが確保できずエラー終了もしくはoom-killerに殺されます。また、GCThreadsなども効率を最大化するためにCPUコア数とスレッド数を同一に設定します(厳密には8コア超えると変わりますが省略します)。これはメモリほどではありませんが、パフォーマンスを低下させる原因となることがあります。詳細は以下の記事にまとまっています。
JavaヒープだけならJava 8u131以降なら-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
で調整してくれるようですが、大抵それだけではなくて他にも調整したいので起動スクリプトでいろいろやることになります。
Red Hatがメンテナンスしている起動スクリプトなどは以下のリポジトリに置いてあります。
https://github.com/jboss-openshift/cct_module/blob/master/os-java-run/added/java-default-options
CPUコア数にもとづいた自動調整はJava VMだけではなく、各種ライブラリでも利用されています。例えば非同期I/Oのワーカースレッドなどは大抵CPUコア数を取得してスレッドプールのサイズを設定します。そういったライブラリの設定もチューニングする必要がでてくるかもしれません。以下のAPIを利用しているものが要注意です。
Runtime.getRuntime().getMaxMemory() Runtime.getRuntime().availableProcessors()
タイトルには「Javaの」と書きましたが、Javaがデフォルトでオートチューニングするためハイライトしただけで、他の言語やライブラリでも同じことをしていれば同じ問題は発生するのでJavaに限定した問題ではないことに注意してください。ライブラリ実装者などはこのようなオートチューニングを実装する時はcgroups対応を考慮すると良いでしょう。
OpenShiftのRouteとRouter
OpenShift 全部俺 Advent Calendar 2017
OpenShiftにはKubernetesで言うところのIngress, Ingress ControllerであるRouteとRouterが標準で利用できます。Kubernetes 1.0のころにはIngressが無かったので、独自に実装されました。
https://docs.openshift.org/3.11/architecture/networking/haproxy-router.html
https://docs.openshift.org/3.11/architecture/networking/routes.html
OpenShift RouterはフロントエンドロードバランサとしてHAProxyを利用しています。また、HAProxyを起動したり、設定を更新したり、リロードしたりするopenshift-routerプロセスがコンテナプロセスとなっています。Routerはhostnetowrkを利用して80ポートと443ポートをLISTENしており、HTTPリクエストのHostヘッダもしくはTLS SNIのホスト名に対応するPodのエンドポイントにトラフィックを転送します。よくある勘違いとしてRouterはServiceのClusterIPにリクエストを転送する、というのがありますが、Serviceは基本的にヘルスチェックが不完全なL4ロードバランスとなっているため、Serviceにリクエストを転送したりはしません。RouterはPodのEndpointをバックエンドサーバとして登録するようになっており、そちらに転送します。oc expose
ではServiceを指定するとRouteが作成されることがこの誤解の原因の一つだと思いますが、設定ファイルhaproxy.config
を見るとそれは正しくないことがわかります。Route経由のアクセスでは、Serviceは設定のためのリソースとしてのみ利用されており、ServiceのClusterIPやL4ロードバランス機能は利用されていません。L7でのヘルスチェックありの振り分け制御となります。
OpenShift Routerはdefaultプロジェクトにデプロイされています。
$ oc project default $ oc get all -o wide NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfigs/docker-registry 1 1 1 config deploymentconfigs/router 1 1 1 config NAME READY STATUS RESTARTS AGE IP NODE po/docker-registry-1-694ml 1/1 Running 2 14d 172.17.0.2 localhost po/persistent-volume-setup-hpd4l 0/1 Completed 0 14d 172.17.0.2 localhost po/router-1-sv5j8 1/1 Running 2 14d 192.168.124.246 localhost NAME DESIRED CURRENT READY AGE CONTAINER(S) IMAGE(S) SELECTOR rc/docker-registry-1 1 1 1 14d registry openshift/origin-docker-registry:v3.7.0 deployment=docker-registry-1,deploymentconfig=docker-registry,docker-registry=default rc/router-1 1 1 1 14d router openshift/origin-haproxy-router:v3.7.0 deployment=router-1,deploymentconfig=router,router=router NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR svc/docker-registry 172.30.1.1 <none> 5000/TCP 14d docker-registry=default svc/kubernetes 172.30.0.1 <none> 443/TCP,53/UDP,53/TCP 14d <none> svc/router 172.30.40.34 <none> 80/TCP,443/TCP,1936/TCP 14d router=router NAME DESIRED SUCCESSFUL AGE CONTAINER(S) IMAGE(S) SELECTOR jobs/persistent-volume-setup 1 1 14d storage-setup-job openshift/origin:v3.7.0 controller-uid=26ab5554-d62e-11e7-ace1-525400f3baa1
docker-registryのRouteを作ってアクセスてみましょう。自分のマシン -> Routerホスト -> RouterのHAProxy -> ターゲットPod、というフローとなります。アプリケーションURLに利用するホスト名はDNSでRouterホストに解決するように設定します。minishiftではdocker-registryはhttpでRouteなしでセットアップされますが、通常のインストールではhttps passthroughのRouteが最初から設定済みなので注意してください。
$ oc expose service docker-registry route "docker-registry" exposed $ oc get route NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD docker-registry docker-registry-default.192.168.42.225.nip.io docker-registry 5000-tcp None $ curl http://docker-registry-default.192.168.42.225.nip.io/v2/ {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}
Router内を見てみましょう。プロセスはopenshift-routerとhaproxyの二つです。
$ oc rsh dc/router sh -c "ps -eaf | cat" UID PID PPID C STIME TTY TIME CMD 1001 1 0 0 Dec11 ? 00:16:49 /usr/bin/openshift-router 1001 248 1 0 Dec11 ? 00:00:16 /usr/sbin/haproxy -f /var/lib/haproxy/conf/haproxy.config -p /var/lib/haproxy/run/haproxy.pid -sf 224
中身の/var/lib/haproxy
の構成は以下のようになっています。
$ oc rsh dc/router sh -c "pwd && ls -Rla /var/lib/haproxy" /var/lib/haproxy/conf /var/lib/haproxy: total 20 drwxrwxr-x. 7 haproxy root 122 Nov 29 16:57 . drwxr-xr-x. 16 root root 211 Nov 29 16:57 .. -rw-rwSr--. 1 1001 1003 30 Nov 13 22:57 .cccp.yml -rw-rwSr--. 1 1001 1003 1327 Nov 13 22:57 Dockerfile drwxrwxr-x. 2 root root 6 Nov 29 16:57 bin drwxrwsr-x. 2 1001 1003 4096 Nov 29 16:22 conf drwxrwxr-x. 2 root root 6 Nov 29 16:57 log -rwxrwsr-x. 1 1001 1003 4979 Nov 13 22:57 reload-haproxy drwxrwxr-x. 4 root root 53 Dec 11 06:07 router drwxrwxr-x. 2 root root 45 Dec 11 06:13 run /var/lib/haproxy/bin: total 0 drwxrwxr-x. 2 root root 6 Nov 29 16:57 . drwxrwxr-x. 7 haproxy root 122 Nov 29 16:57 .. /var/lib/haproxy/conf: total 72 drwxrwsr-x. 2 1001 1003 4096 Nov 29 16:22 . drwxrwxr-x. 7 haproxy root 122 Nov 29 16:57 .. -rw-rw-r--. 1 root root 0 Dec 11 06:13 cert_config.map -rw-rwSr--. 1 1001 1003 2035 Nov 13 22:57 default_pub_keys.pem -rw-rwSr--. 1 1001 1003 3278 Nov 13 22:57 error-page-503.http -rw-rwSr--. 1 1001 1003 30599 Nov 13 22:57 haproxy-config.template -rw-rw-r--. 1 root root 10051 Dec 11 06:13 haproxy.config -rw-rw-r--. 1 root root 88 Dec 11 06:13 os_edge_http_be.map -rw-rw-r--. 1 root root 94 Dec 11 06:13 os_http_be.map -rw-rw-r--. 1 root root 0 Dec 11 06:13 os_reencrypt.map -rw-rw-r--. 1 root root 0 Dec 11 06:13 os_route_http_expose.map -rw-rw-r--. 1 root root 88 Dec 11 06:13 os_route_http_redirect.map -rw-rw-r--. 1 root root 0 Dec 11 06:13 os_sni_passthrough.map -rw-rw-r--. 1 root root 0 Dec 11 06:13 os_tcp_be.map -rw-rw-r--. 1 root root 1 Dec 11 06:13 os_wildcard_domain.map /var/lib/haproxy/log: total 0 drwxrwxr-x. 2 root root 6 Nov 29 16:57 . drwxrwxr-x. 7 haproxy root 122 Nov 29 16:57 .. /var/lib/haproxy/router: total 4 drwxrwxr-x. 4 root root 53 Dec 11 06:07 . drwxrwxr-x. 7 haproxy root 122 Nov 29 16:57 .. drwxrwxr-x. 2 root root 6 Nov 29 16:57 cacerts drwxrwxr-x. 2 root root 6 Nov 29 16:57 certs -rw-r--r--. 1 1001 root 1378 Dec 11 06:13 routes.json /var/lib/haproxy/router/cacerts: total 0 drwxrwxr-x. 2 root root 6 Nov 29 16:57 . drwxrwxr-x. 4 root root 53 Dec 11 06:07 .. /var/lib/haproxy/router/certs: total 0 drwxrwxr-x. 2 root root 6 Nov 29 16:57 . drwxrwxr-x. 4 root root 53 Dec 11 06:07 .. /var/lib/haproxy/run: total 4 drwxrwxr-x. 2 root root 45 Dec 11 06:13 . drwxrwxr-x. 7 haproxy root 122 Nov 29 16:57 .. -rw-r--r--. 1 1001 root 4 Dec 11 06:13 haproxy.pid srw-------. 1 1001 root 0 Dec 11 06:13 haproxy.sock
openshift-routerはhaproxy-config.template
からhaproxy.config
ファイルを生成しますが、テンプレートファイルの読み込みは起動時一回のみ行われるのでカスタマイズしたい場合にoc rsh
などでコンテナ内で書き換えても意味がないことに注意してください。ConfigMapで差し替えることができます。
HAProxyはログ出力先としてsyslogしかサポートしていないため、運用環境では少なくともROUTER_SYSLOG_ADDRESSは設定しましょう。これは扱いづらいので、OpenShiftでもどうにかしようという議論は長く続いていますが、まだ解決されていません。
追記: syslogコンテナをsidecarにする方法などがあります。
ドキュメントに載っているアノテーションや環境変数での設定などの他に、コマンドラインパラメータにもいくつか設定があるので載せておきます。
$ oc rsh dc/router openshift-router -h Start a router This command launches a router connected to your cluster master. The router listens for routes and endpoints created by users and keeps a local router configuration up to date with those changes. You may customize the router by providing your own --template and --reload scripts. The router must have a default certificate in pem format. You may provide it via --default-cert otherwise one is automatically created. You may restrict the set of routes exposed to a single project (with --namespace), projects your client has access to with a set of labels (--project-labels), namespaces matching a label (--namespace-labels), or all namespaces (no argument). You can limit the routes to those matching a --labels or --fields selector. Note that you must have a cluster-wide administrative role to view all namespaces. Usage: openshift-router --master=<addr> [flags] openshift-router [command] Available Commands: version Display client and server versions Flags: --allow-wildcard-routes Allow wildcard host names for routes --allowed-domains stringSlice List of comma separated domains to allow in routes. If specified, only the domains in this list will be allowed routes. Note that domains in the denied list take precedence over the ones in the allowed list --as string Username to impersonate for the operation --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. --azure-container-registry-config string Path to the file container Azure container registry configuration information. --bind-ports-after-sync Bind ports only after route state has been synchronized --certificate-authority string Path to a cert file for the certificate authority --ciphers string Specifies the cipher suites to use. You can choose a predefined cipher set ('modern', 'intermediate', or 'old') or specify exact cipher suites by passing a : separated list. --client-certificate string Path to a client certificate file for TLS --client-key string Path to a client key file for TLS --cluster string The name of the kubeconfig cluster to use --config string Path to the config file to use for CLI requests. --context string The name of the kubeconfig context to use --default-certificate string The contents of a default certificate to use for routes that don't expose a TLS server cert; in PEM format --default-certificate-dir string A path to a directory that contains a file named tls.crt. If tls.crt is not a PEM file which also contains a private key, it is first combined with a file named tls.key in the same directory. The PEM-format contents are then used as the default certificate. Only used if default-certificate and default-certificate-path are not specified. (default "/etc/pki/tls/private") --default-certificate-path string A path to default certificate to use for routes that don't expose a TLS server cert; in PEM format (default "/etc/pki/tls/private/tls.crt") --default-destination-ca-path string A path to a PEM file containing the default CA bundle to use with re-encrypt routes. This CA should sign for certificates in the Kubernetes DNS space (service.namespace.svc). (default "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt") --denied-domains stringSlice List of comma separated domains to deny in routes --disable-namespace-ownership-check Disables the namespace ownership checks for a route host with different paths or for overlapping host names in the case of wildcard routes. Please be aware that if namespace ownership checks are disabled, routes in a different namespace can use this mechanism to 'steal' sub-paths for existing domains. This is only safe if route creation privileges are restricted, or if all the users can be trusted. --enable-ingress Enable configuration via ingress resources --extended-validation If set, then an additional extended validation step is performed on all routes admitted in by this router. Defaults to true and enables the extended validation checks. (default true) --fields string A field selector to apply to routes to watch --google-json-key string The Google Cloud Platform Service Account JSON Key to use for authentication. --hostname-template string If specified, a template that should be used to generate the hostname for a route without spec.host (e.g. '${name}-${namespace}.myapps.mycompany.com') --include-udp-endpoints If true, UDP endpoints will be considered as candidates for routing --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure --interval duration Controls how often router reloads are invoked. Mutiple router reload requests are coalesced for the duration of this interval since the last reload time. (default 5s) --kubernetes string The address of the Kubernetes server (host, host:port, or URL). If omitted defaults to the master. (default "http://localhost:8080") --labels string A label selector to apply to the routes to watch --listen-addr string The name of an interface to listen on to expose metrics and health checking. If not specified, will not listen. Overrides stats port. (default "0.0.0.0:1936") --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) --loglevel int32 Set the level of log output (0-10) --logspec string Set per module logging with file|pattern=LEVEL,... --master string The address the master can be reached on (host, host:port, or URL). (default "http://localhost:8080") --max-connections string Specifies the maximum number of concurrent connections. --metrics-type string Specifies the type of metrics to gather. Supports 'haproxy'. (default "haproxy") --name string The name the router will identify itself with in the route status (default "router") -n, --namespace string If present, the namespace scope for this CLI request --namespace-labels string A label selector to apply to namespaces to watch --override-hostname Override the spec.host value for a route with --hostname-template --project-labels string A label selector to apply to projects to watch; if '*' watches all projects the client can access --reload string The path to the reload script to use (default "/var/lib/haproxy/reload-haproxy") --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") --resync-interval duration The interval at which the route list should be fully refreshed (default 10m0s) --router-canonical-hostname string CanonicalHostname is the external host name for the router that can be used as a CNAME for the host requested for this route. This value is optional and may not be set in all cases. --server string The address and port of the Kubernetes API server --stats-password string If the underlying router implementation can provide statistics this is the requested password for auth. (default "x6RVrWmXwV") --stats-port string If the underlying router implementation can provide statistics this is a hint to expose it on this port. Ignored if listen-addr is specified. (default "1936") --stats-user string If the underlying router implementation can provide statistics this is the requested username for auth. (default "admin") --strict-sni Use strict-sni bind processing (do not use default cert). --template string The path to the template file to use (default "/var/lib/haproxy/conf/haproxy-config.template") --token string Bearer token for authentication to the API server --user string The name of the kubeconfig user to use --version version[=true] Print version information and quit --working-dir string The working directory for the router plugin (default "/var/lib/haproxy/router") Use "openshift-router [command] --help" for more information about a command.
OpenShiftでStatefulSetを使ってみる
OpenShift 全部俺 Advent Calendar 2017
今までは普通のデプロイメントを利用していましたが、今回はちょっと特殊な性格を持つStatefulSetを使ってみましょう。StatefulSetのレプリカは連番のsuffixが付与され、スケール時の順序制御があったりvolumeClaimTemplatesによる個々のPodへそれぞれPVを割り当てたりできます。主にクラスタ系の分散前提のソフトウェアに利用されます。
https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
StatefulSetはKubernetes 1.8の時点でbeta機能であり、OpenShiftでも3.5から含まれてはいますがまだフルサポートされていません。
いつものRubyプロジェクトをベースにします。
$ oc new-project test-ruby $ oc new-app http://github.com/nekop/hello-sinatra $ oc get all NAME TYPE FROM LATEST buildconfigs/hello-sinatra Source Git 1 NAME TYPE FROM STATUS STARTED DURATION builds/hello-sinatra-1 Source Git@cf28c79 Complete 13 minutes ago 47s NAME DOCKER REPO TAGS UPDATED imagestreams/hello-sinatra docker-registry.default.svc:5000/test-ruby/hello-sinatra latest 12 minutes ago NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfigs/hello-sinatra 1 1 1 config,image(hello-sinatra:latest) NAME READY STATUS RESTARTS AGE po/hello-sinatra-1-build 0/1 Completed 0 13m po/hello-sinatra-1-s2ls7 1/1 Running 0 12m NAME DESIRED CURRENT READY AGE rc/hello-sinatra-1 1 1 1 12m NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/hello-sinatra 172.30.82.101 <none> 8080/TCP 13m
ここでビルドされたイメージをデプロイするStatefulSetを作ってみましょう。まだbeta機能なのでyamlを直接記述する必要があります。
$ cat <<EOF | oc create -f - apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: hello-sinatra-ss spec: replicas: 2 serviceName: hello-sinatra-ss template: metadata: labels: app: hello-sinatra-ss spec: containers: - image: docker-registry.default.svc:5000/test-ruby/hello-sinatra:latest imagePullPolicy: Always name: hello-sinatra-ss ports: - containerPort: 8080 protocol: TCP resources: {} --- apiVersion: v1 kind: Service metadata: labels: app: hello-sinatra-ss name: hello-sinatra-ss spec: ports: - name: 8080-tcp port: 8080 protocol: TCP targetPort: 8080 selector: app: hello-sinatra-ss EOF
これでStatefulSetが作成され、指定した通りレプリカが2つ起動しています。
$ oc get all NAME TYPE FROM LATEST buildconfigs/hello-sinatra Source Git 1 NAME TYPE FROM STATUS STARTED DURATION builds/hello-sinatra-1 Source Git@cf28c79 Complete About a minute ago 47s NAME DOCKER REPO TAGS UPDATED imagestreams/hello-sinatra docker-registry.default.svc:5000/test-ruby/hello-sinatra latest About a minute ago NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfigs/hello-sinatra 1 1 1 config,image(hello-sinatra:latest) NAME READY STATUS RESTARTS AGE po/hello-sinatra-1-build 0/1 Completed 0 1m po/hello-sinatra-1-s2ls7 1/1 Running 0 1m po/hello-sinatra-ss-0 1/1 Running 0 12s po/hello-sinatra-ss-1 1/1 Running 0 9s NAME DESIRED CURRENT READY AGE rc/hello-sinatra-1 1 1 1 1m NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/hello-sinatra 172.30.82.101 <none> 8080/TCP 1m svc/hello-sinatra-ss 172.30.165.8 <none> 8080/TCP 12s NAME DESIRED CURRENT AGE statefulsets/hello-sinatra-ss 2 2 12s
OpenShiftのs2iイメージをカスタマイズする
OpenShift 全部俺 Advent Calendar 2017
昨日は忘年会でした。全部俺 Advent Calendarがすごく大変そう、と言われましたが各エントリ20分程度で書いていますし、そうなるように内容を調整しています。例えば昨日のエントリは17時くらいに同僚に何書いて欲しい?と聞いてから最新の情報をちょっと調べて書いて飲みに行った、くらいのノリです。
今日はデフォルトのs2iイメージをカスタマイズしてみましょう。例ではRubyを利用します。
$ oc new-project test-custom-s2i $ oc new-app http://github.com/nekop/hello-sinatra $ oc get all oc NAME TYPE FROM LATEST buildconfigs/hello-sinatra Source Git 1 NAME TYPE FROM STATUS STARTED DURATION builds/hello-sinatra-1 Source Git@cf28c79 Running 49 seconds ago NAME DOCKER REPO TAGS UPDATED imagestreams/hello-sinatra docker-registry.default.svc:5000/test-custom-s2i/hello-sinatra NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfigs/hello-sinatra 0 1 0 config,image(hello-sinatra:latest) NAME READY STATUS RESTARTS AGE po/hello-sinatra-1-build 1/1 Running 0 49s
oc describe bc hello-sinatra
、そしてoc describe is ruby -n openshift
と追っていくと、利用されているImageStreamruby:2.4
はregistry.access.redhat.com/rhscl/ruby-24-rhel7:latest
を参照していることがわかります。
このイメージをDockerfileビルドを利用してカスタマイズしていきます。今回は単純にtouch /foobar
などとしてみます。
cat <<EOF | oc new-build --dockerfile=- --to=custom-ruby FROM registry.access.redhat.com/rhscl/ruby-24-rhel7:latest USER root RUN touch /foobar USER 1001 EOF
ビルドが開始されます。
$ oc get all NAME TYPE FROM LATEST buildconfigs/custom-ruby Docker Dockerfile 1 buildconfigs/hello-sinatra Source Git 1 NAME TYPE FROM STATUS STARTED DURATION builds/custom-ruby-1 Docker Dockerfile Pending builds/hello-sinatra-1 Source Git@cf28c79 Complete 10 minutes ago 1m3s NAME DOCKER REPO TAGS UPDATED imagestreams/custom-ruby docker-registry.default.svc:5000/test-custom-s2i/custom-ruby imagestreams/hello-sinatra docker-registry.default.svc:5000/test-custom-s2i/hello-sinatra latest 9 minutes ago imagestreams/ruby-24-rhel7 docker-registry.default.svc:5000/test-custom-s2i/ruby-24-rhel7 latest 5 seconds ago NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfigs/hello-sinatra 1 1 1 config,image(hello-sinatra:latest) NAME READY STATUS RESTARTS AGE po/custom-ruby-1-build 0/1 Init:0/1 0 4s po/hello-sinatra-1-build 0/1 Completed 0 10m po/hello-sinatra-1-ddx5v 1/1 Running 0 9m NAME DESIRED CURRENT READY AGE rc/hello-sinatra-1 1 1 1 9m NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/hello-sinatra 172.30.223.174 <none> 8080/TCP 10m
ビルドが終わったらbc/hello-sinatra
のspec.strategy.sourceStragtegyのruby:2.4
をcustom-ruby:latest
に変更します。これでカスタマイズしたs2iイメージが利用されます。
$ oc edit bc/hello-sinatra sourceStrategy: from: kind: ImageStreamTag name: ruby:2.4 namespace: openshift
変更後はこうなります。
sourceStrategy: from: kind: ImageStreamTag name: custom-ruby:latest
BuildConfigはConfigChange triggerがデフォルトで有効になっているので、ビルドが自動的に開始され、レジストリにpushされ、DeploymentConfigのImageChange triggerが発動してデプロイされます。
$ oc get all NAME TYPE FROM LATEST buildconfigs/custom-ruby Docker Dockerfile 1 buildconfigs/hello-sinatra Source Git 2 NAME TYPE FROM STATUS STARTED DURATION builds/custom-ruby-1 Docker Dockerfile Complete 6 minutes ago 54s builds/hello-sinatra-1 Source Git@cf28c79 Complete 16 minutes ago 1m3s builds/hello-sinatra-2 Source Git@cf28c79 Complete 4 minutes ago 19s NAME DOCKER REPO TAGS UPDATED imagestreams/custom-ruby docker-registry.default.svc:5000/test-custom-s2i/custom-ruby latest 5 minutes ago imagestreams/hello-sinatra docker-registry.default.svc:5000/test-custom-s2i/hello-sinatra latest 3 minutes ago imagestreams/ruby-24-rhel7 docker-registry.default.svc:5000/test-custom-s2i/ruby-24-rhel7 latest 6 minutes ago NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfigs/hello-sinatra 2 1 1 config,image(hello-sinatra:latest) NAME READY STATUS RESTARTS AGE po/custom-ruby-1-build 0/1 Completed 0 6m po/hello-sinatra-1-build 0/1 Completed 0 16m po/hello-sinatra-2-build 0/1 Completed 0 4m po/hello-sinatra-2-t4dpb 1/1 Running 0 3m NAME DESIRED CURRENT READY AGE rc/hello-sinatra-1 0 0 0 15m rc/hello-sinatra-2 1 1 1 3m NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/hello-sinatra 172.30.223.174 <none> 8080/TCP 16m
新しくデプロイされたコンテナに入ると、ちゃんとカスタマイズが反映されています。
$ oc rsh dc/hello-sinatra ls -la /foobar -rw-r--r--. 1 root root 0 Dec 13 08:41 /foobar
OpenShiftのRBACを完全に理解する
OpenShift 全部俺 Advent Calendar 2017
よくわかりにくいと言われがちなOpenShift / KubernetesのRBACについて書いてみましょう。RBACは元々OpenShiftで実装され、それを元にKubernetes側へ実装された経緯があり、OpenShiftのclusterrole
というリソースオブジェクトとKubernetesのclusterrole
リソースの短縮名が衝突してしまっています。そのため、OpenShift側ではclusterrole.rbac
という名前でリソースを指定する必要があります。ちなみに省略しないリソース名はそれぞれclusterrole.authorization.openshift.io
とclusterrole.rbac.authorization.k8s.io
です。
さて本題。事前に定義されているロールの一覧はoc get clusterrole.rbac
で取得できます。
$ oc get clusterrole.rbac NAME AGE admin 12d asb-access 12d asb-auth 12d basic-user 12d cluster-admin 12d cluster-debugger 12d cluster-reader 12d cluster-status 12d edit 12d hawkular-metrics-admin 12d management-infra-admin 12d namespace-viewer 12d registry-admin 12d registry-editor 12d registry-viewer 12d sar-creator 12d self-access-reviewer 12d self-provisioner 12d service-catalog-controller 12d servicecatalog-serviceclass-viewer 12d storage-admin 12d sudoer 12d system:auth-delegator 12d system:basic-user 12d system:build-controller 12d system:build-strategy-custom 12d system:build-strategy-docker 12d system:build-strategy-jenkinspipeline 12d system:build-strategy-source 12d system:certificate-signing-controller 12d system:controller:attachdetach-controller 12d system:controller:certificate-controller 12d system:controller:cronjob-controller 12d system:controller:daemon-set-controller 12d system:controller:deployment-controller 12d system:controller:disruption-controller 12d system:controller:endpoint-controller 12d system:controller:generic-garbage-collector 12d system:controller:horizontal-pod-autoscaler 12d system:controller:job-controller 12d system:controller:namespace-controller 12d system:controller:node-controller 12d system:controller:persistent-volume-binder 12d system:controller:pod-garbage-collector 12d system:controller:replicaset-controller 12d system:controller:replication-controller 12d system:controller:resourcequota-controller 12d system:controller:route-controller 12d system:controller:service-account-controller 12d system:controller:service-controller 12d system:controller:statefulset-controller 12d system:controller:ttl-controller 12d system:daemonset-controller 12d system:deployer 12d system:deployment-controller 12d system:deploymentconfig-controller 12d system:discovery 12d system:disruption-controller 12d system:endpoint-controller 12d system:garbage-collector-controller 12d system:gc-controller 12d system:heapster 12d system:hpa-controller 12d system:image-auditor 12d system:image-builder 12d system:image-pruner 12d system:image-puller 12d system:image-pusher 12d system:image-signer 12d system:job-controller 12d system:kube-aggregator 12d system:kube-controller-manager 12d system:kube-dns 12d system:kube-scheduler 12d system:master 12d system:namespace-controller 12d system:node 12d system:node-admin 12d system:node-bootstrapper 12d system:node-problem-detector 12d system:node-proxier 12d system:node-reader 12d system:oauth-token-deleter 12d system:openshift:controller:build-config-change-controller 12d system:openshift:controller:build-controller 12d system:openshift:controller:cluster-quota-reconciliation-controller 12d system:openshift:controller:deployer-controller 12d system:openshift:controller:deploymentconfig-controller 12d system:openshift:controller:horizontal-pod-autoscaler 12d system:openshift:controller:image-import-controller 12d system:openshift:controller:image-trigger-controller 12d system:openshift:controller:origin-namespace-controller 12d system:openshift:controller:pv-recycler-controller 12d system:openshift:controller:resourcequota-controller 12d system:openshift:controller:sdn-controller 12d system:openshift:controller:service-ingress-ip-controller 12d system:openshift:controller:service-serving-cert-controller 12d system:openshift:controller:serviceaccount-controller 12d system:openshift:controller:serviceaccount-pull-secrets-controller 12d system:openshift:controller:template-instance-controller 12d system:openshift:controller:template-service-broker 12d system:openshift:controller:unidling-controller 12d system:openshift:templateservicebroker-client 12d system:persistent-volume-provisioner 12d system:registry 12d system:replicaset-controller 12d system:replication-controller 12d system:router 12d system:scope-impersonation 12d system:sdn-manager 12d system:sdn-reader 12d system:statefulset-controller 12d system:webhook 12d view 12d
なんだかすごくいっぱい出てきましたが、system:
とついているものはシステムコンポーネントで利用されているロールで利用者視点では真っ先に無視して構わないものです。除外してみます。
$ oc get clusterrole.rbac | grep -v system: NAME AGE admin 12d asb-access 12d asb-auth 12d basic-user 12d cluster-admin 12d cluster-debugger 12d cluster-reader 12d cluster-status 12d edit 12d hawkular-metrics-admin 12d management-infra-admin 12d namespace-viewer 12d registry-admin 12d registry-editor 12d registry-viewer 12d sar-creator 12d self-access-reviewer 12d self-provisioner 12d service-catalog-controller 12d servicecatalog-serviceclass-viewer 12d storage-admin 12d sudoer 12d view 12d
いくつかまだOpenShiftが利用する目的のロールであり利用者には無関係なロールがあるものの、だいぶすっきりしました。
各ロールが実際に何ができるかはdescribeすればわかります。たとえばnamespace-viewer
はnamespace
オブジェクトのget list watch
が許可されているロールです。
$ oc describe clusterrole.rbac namespace-viewer Name: namespace-viewer Labels: <none> Annotations: <none> PolicyRule: Resources Non-Resource URLs Resource Names Verbs --------- ----------------- -------------- ----- namespaces [] [] [get list watch]
カスタムのロールを作るときにResourcesやVerbsに何が指定できるかはoc describe clusterrole.rbac
などで全出力を見ればわかるでしょう。カスタムロールはプロジェクト(namespace)固有のものはrole.rbac
として、クラスタ全体で利用したいロールは管理者権限でclusterrole.rbac
として作成します。
ロールの割り当て状況はoc get clusterrolebinding.rbac -o wide
を確認するとわかります。割り当てられていないロールは非表示となるので注意しましょう。以下system:
を除外した結果です。
$ oc get clusterrolebinding.rbac -o wide | grep -v system: NAME AGE ROLE USERS GROUPS SERVICEACCOUNTS admin 12d admin openshift-infra/template-instance-controller, kube-service-catalog/default, openshift-ansible-service-broker/asb asb-access 12d asb-access openshift-ansible-service-broker/asb-client asb-auth 12d asb-auth openshift-ansible-service-broker/asb hawkular-metrics-admin 12d hawkular-metrics-admin management-infra/management-admin management-infra-admin 12d management-infra-admin management-infra/management-admin service-catalog-controller-binding 12d service-catalog-controller kube-service-catalog/service-catalog-controller service-catalog-controller-namespace-viewer-binding 12d namespace-viewer kube-service-catalog/service-catalog-controller service-catalog-namespace-viewer-binding 12d namespace-viewer kube-service-catalog/service-catalog-apiserver service-catalog-sar-creator-binding 12d sar-creator kube-service-catalog/service-catalog-apiserver
User, Group, Service Accountというのが出てきました。ユーザは一般的なユーザ、およびシステムユーザが含まれます。グループは一般的なグループ、システムグループ、特殊なシステムグループであるバーチャルグループがあります。
system:admin
などのシステムユーザやシステムグループのドキュメントや一覧は用意されていませんが、なんとなく名前で役割はわかると思います。これらはソースコード中にハードコードされているので、oc get user
, oc get group
では表示されず、上記のclusterrolebinding.rbac
の出力が唯一の手掛かりとなります。
system:authenticated
やsystem:unauthenticated
は名前の通り、認証済みユーザ、認証していないユーザを表すバーチャルシステムグループです。これを利用して、例えばsystem:unauthenticated
にregistry-viewer
を付与すると、そのプロジェクトのコンテナイメージは認証なしでpullできる状態となります。
Userは人間が操作するユーザに割り当てるのに対し、Service Account (SA)は、Podに割り当てるユーザです。権限設定が必要なPodや、tokenを抜き出して外部から操作したりといった各種自動化などに利用されます。たとえば各プロジェクトにはdefault
, builder
, deployer
というSAが自動的に作成され、deployer
にはプロジェクトのロールとしてsystem:deployer
が設定されます。これはoc get rolebinding.rbac -o wide
で確認できます。確認するとわかりますが、Podがデフォルトで利用するSAであるdefault
は初期状態ではロールが割り当てられていない一般ユーザとなり、Kubernetes APIへのアクセスは一切できません。
一般的に利用されるロールを軽く説明します。
cluster-admin
- クラスタ管理者権限。一般ユーザに付与するには
oc adm policy add-cluster-role-to-user cluster-admin USERNAME
。
- クラスタ管理者権限。一般ユーザに付与するには
cluster-reader
view
,edit
,admin
registry-viewer
,registry-editor
- プロジェクト内のコンテナイメージのpullおよびpull/push権限。別プロジェクトのSAに付与して利用する。
というわけで、以下の4つさえ押さえておけばRBAC完璧です。
OpenShiftのPipelineビルドで利用するイメージをカスタマイズする
OpenShift 全部俺 Advent Calendar 2017
Google Home買いました。日本語設定でいろいろやってみて、3歳児が「おーけーぐるぐる、てべりをちゅけてください、おねがいします!」と話しかけてるのを一通り楽しんでから英語設定にスイッチして使ってます。これで子供が英語覚えたりするかな。まぁしないだろうな。
前回Jenkins PipelineジョブをSlaveとなる別のPodで実行しました。今回はこのslave podをカスタマイズしてみましょう。ドキュメントは以下のURLです。
https://docs.openshift.org/3.11/using_images/other_images/jenkins.html
https://github.com/jenkinsci/kubernetes-plugin
config.xml
にデフォルトの定義maven
とnode
があるので、それをコピーして新しい設定を作成します。適当にgrepして一つ目を取り出してみます。ConfigMapに突っ込むファイルはCRLFだとEditするときにひどいことになるのでLFに変換します。
$ oc rsh dc/jenkins cat /var/lib/jenkins/config.xml | grep -m1 -A33 PodTemplate | perl -pe 's/^\s{8}//' | dos2unix > pod-template.xml
中身はmaven
のもので、以下のような定義となっています。
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate> <inheritFrom></inheritFrom> <name>maven</name> <privileged>false</privileged> <alwaysPullImage>false</alwaysPullImage> <instanceCap>2147483647</instanceCap> <slaveConnectTimeout>0</slaveConnectTimeout> <idleMinutes>0</idleMinutes> <label>maven</label> <serviceAccount>jenkins</serviceAccount> <nodeSelector></nodeSelector> <customWorkspaceVolumeEnabled>false</customWorkspaceVolumeEnabled> <volumes/> <containers> <org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate> <name>jnlp</name> <image>openshift/jenkins-slave-maven-centos7</image> <privileged>false</privileged> <alwaysPullImage>false</alwaysPullImage> <workingDir>/tmp</workingDir> <command></command> <args>${computer.jnlpmac} ${computer.name}</args> <ttyEnabled>false</ttyEnabled> <resourceRequestCpu></resourceRequestCpu> <resourceRequestMemory></resourceRequestMemory> <resourceLimitCpu></resourceLimitCpu> <resourceLimitMemory></resourceLimitMemory> <envVars/> </org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate> </containers> <envVars/> <annotations/> <imagePullSecrets/> </org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
name
が作成されるslave pod名、label
がPipelineで指定するslaveのラベル名になっています。
両方custom-maven
に変更して、環境変数を追加してビルドしてみましょう。
<org.csanchez.jenkins.plugins.kubernetes.PodTemplate> <inheritFrom></inheritFrom> <name>custom-maven</name> <privileged>false</privileged> <alwaysPullImage>false</alwaysPullImage> <instanceCap>2147483647</instanceCap> <slaveConnectTimeout>0</slaveConnectTimeout> <idleMinutes>0</idleMinutes> <label>custom-maven</label> <serviceAccount>jenkins</serviceAccount> <nodeSelector></nodeSelector> <customWorkspaceVolumeEnabled>false</customWorkspaceVolumeEnabled> <volumes/> <containers> <org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate> <name>jnlp</name> <image>openshift/jenkins-slave-maven-centos7</image> <privileged>false</privileged> <alwaysPullImage>false</alwaysPullImage> <workingDir>/tmp</workingDir> <command></command> <args>${computer.jnlpmac} ${computer.name}</args> <ttyEnabled>false</ttyEnabled> <resourceRequestCpu></resourceRequestCpu> <resourceRequestMemory></resourceRequestMemory> <resourceLimitCpu></resourceLimitCpu> <resourceLimitMemory></resourceLimitMemory> <envVars> <org.csanchez.jenkins.plugins.kubernetes.PodEnvVar> <key>foo</key> <value>bar</value> </org.csanchez.jenkins.plugins.kubernetes.PodEnvVar> </envVars> </org.csanchez.jenkins.plugins.kubernetes.ContainerTemplate> </containers> <envVars/> <annotations/> <imagePullSecrets/> </org.csanchez.jenkins.plugins.kubernetes.PodTemplate>
role=jenkins-slave
というラベルが付与されているConfigMapに入れるとOpenShift Jenkins Sync Pluginが勝手に反映してくれるようになっています。
$ vi pod-template.xml $ oc create configmap jenkins-slave --from-file=pod-template.xml $ oc label configmap jenkins-slave role=jenkins-slave $ oc start-build test-pipeline
これで生成されたcustom-maven-XXXXというPodではfoo=bar
という環境変数が設定されている状態となります。Pipelineでsh "echo $foo"
など実行することで確認できます。
OpenShiftでJenkins Pipelineビルドを利用する
OpenShift 全部俺 Advent Calendar 2017
OpenShiftにはソースコードからビルドするs2iビルド、Dockerfileを利用してビルドするDockerビルドの他にJenkinsを利用したPipelineビルドというのがあります。s2iビルドやDockerビルドはコンテナイメージを生成してpushする、というビルドですが、PipelineビルドはJenkinsで実行する、という少し性格の異なるビルドになっています。
PipelineビルドもJenkinsfileがあるGit URLをoc new-app
で渡すことができますが、Dockerfileのように埋め込むこともできます。個人的にはテスト目的での使用が多いので、埋め込みスタイルを多用しています。
oc new-build
には--dockerfile相当の--jenkinsfileのようなオプションは実装されていないので、Jenkinsfine埋め込みyamlを直接ぶち込みます。
$ cat <<EOF | oc create -f - kind: BuildConfig apiVersion: v1 metadata: name: test-pipeline labels: name: test-pipeline spec: strategy: type: JenkinsPipeline jenkinsPipelineStrategy: jenkinsfile: |- pipeline { agent { label 'maven' } stages { stage('Stage 1') { steps { sh "echo Stage 1" } } stage('Stage 2') { steps { sh "echo Stage 2" } } } } EOF
pipeline、旧syntaxではnode('maven') { sh "echo test" }
で大丈夫なのですが、新しいsyntaxになってからブロックのネストがいっぱいになってちょっと冗長な雰囲気になりました。
例では"echo Stage 1"などの実行しかしていませんが、別のビルドを実行したりイメージを別環境にリリースしたり、統合テストをキックしたりなど好きなことができます。いろいろやっている例が https://blog.openshift.com/building-declarative-pipelines-openshift-dsl-plugin/ に掲載されています。
Pipelineビルドを作成すると、実行のためのJenkinsが自動でデプロイされます。
$ oc get all NAME TYPE FROM LATEST buildconfigs/test-pipeline JenkinsPipeline 0 NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfigs/jenkins 1 1 1 config,image(jenkins:latest) NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD routes/jenkins jenkins-test-jenkins.192.168.42.143.nip.io jenkins <all> edge/Redirect None NAME READY STATUS RESTARTS AGE po/jenkins-1-dl7tq 1/1 Running 0 4m NAME DESIRED CURRENT READY AGE rc/jenkins-1 1 1 1 4m NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE svc/jenkins 172.30.211.203 <none> 80/TCP 4m svc/jenkins-jnlp 172.30.28.12 <none> 50000/TCP 4m $ oc start-build test-pipeline
Jenkinsのログをみると、agent指定されているのでagentとなる新しいPodをスケジューリングして実行しているのがわかります。
$ oc logs dc/jenkins Dec 08, 2017 9:37:57 AM io.fabric8.jenkins.openshiftsync.BuildConfigWatcher updateJob INFO: Updated job test-jenkins-test-pipeline from BuildConfig NamespaceName{test-jenkins:test-pipeline} with revision: 111669 Dec 08, 2017 9:37:57 AM io.fabric8.jenkins.openshiftsync.BuildSyncRunListener onStarted INFO: starting polling build job/test-jenkins-test-pipeline/1/ Dec 08, 2017 9:38:01 AM org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader$4$1 load WARNING: took 334ms to load/not load jenkins.model.Class from classLoader hudson.PluginManager$UberClassLoader Dec 08, 2017 9:38:16 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud provision INFO: Excess workload after pending Spot instances: 1 Dec 08, 2017 9:38:16 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesCloud provision INFO: Template: Kubernetes Pod Template Dec 08, 2017 9:38:17 AM okhttp3.internal.platform.Platform log INFO: ALPN callback dropped: HTTP/2 is disabled. Is alpn-boot on the boot class path? Dec 08, 2017 9:38:17 AM hudson.slaves.NodeProvisioner$StandardStrategyImpl apply INFO: Started provisioning Kubernetes Pod Template from openshift with 1 executors. Remaining excess workload: 0 Dec 08, 2017 9:38:18 AM okhttp3.internal.platform.Platform log INFO: ALPN callback dropped: HTTP/2 is disabled. Is alpn-boot on the boot class path? Dec 08, 2017 9:38:18 AM org.csanchez.jenkins.plugins.kubernetes.ProvisioningCallback call INFO: Created Pod: maven-vb87g in namespace test-jenkins Dec 08, 2017 9:38:18 AM org.csanchez.jenkins.plugins.kubernetes.ProvisioningCallback call INFO: Waiting for Pod to be scheduled (0/100): maven-vb87g Dec 08, 2017 9:38:24 AM okhttp3.internal.platform.Platform log INFO: ALPN callback dropped: HTTP/2 is disabled. Is alpn-boot on the boot class path? Dec 08, 2017 9:38:24 AM org.csanchez.jenkins.plugins.kubernetes.ProvisioningCallback call INFO: Container is waiting maven-vb87g [jnlp]: ContainerStateWaiting(message=null, reason=ContainerCreating, additionalProperties={}) Dec 08, 2017 9:38:24 AM org.csanchez.jenkins.plugins.kubernetes.ProvisioningCallback call INFO: Waiting for Pod to be scheduled (1/100): maven-vb87g
このPod maven-vb87g
の中で指定したステップが実行されます。
Pipelineビルドの状況はWebコンソールから確認でき、View Logを押すとJenkinsの画面に遷移してログを確認できます。
次は'maven'となっている部分の説明とカスタマイズについて書く予定です。