エフェメラルコンテナでJDKのデバッグツールを使う夢を見つつ、Podとコンテナのライフサイクルに思いを馳せる

気づいたらだいぶブログをサボっていました…。

これはKubernetesアドベントカレンダーの19日目のエントリーです。メリー・クリスマス!

エフェメラルコンテナ x JVMデバッグツールという、誰得な話を書きたいと思います。

1 . エフェメラルコンテナとは

エフェメラルコンテナってなに?という解説は、@superbrothersさんが公開してくださっている以下の資料を見るのが一番ですので、ここでは簡単な説明に留めます。

エフェメラルコンテナは、実行中のPodにエフェメラルな(揮発的な、一時的な)コンテナを後から追加する機能です。 一般的には、コンテナには、動かしたいアプリケーションが必要としないコマンドやモジュールは極力含めないようにするのが良いとされています。 エフェメラルコンテナがあると、デバッグのためのコマンド等をそちらに入れて実行できるようになります。このため、アプリケーションのコンテナからデバッグのためだけに必要なコマンドやモジュールを排除することができ、理想的なコンテナが作成できるというわけです。

f:id:charlier_shoe:20191219033039p:plain
エフェメラルコンテナ

2 . エフェメラルコンテナからJDKデバッグツールを使ってみる

ここから本題ですが、Java, Scala, Kotolinなど、JVMベースのアプリケーションをKubernetesで動かすことを考えてみます。 代用的なJDKディストリビューションのひとつであるOpenJDKには、jps, jmcd, jstack, jfrといった、JVMアプリのデバッグトラブルシューティングのための便利なツール群が同梱されています。1

しかし、これらは JDK(Java Development Kit) の一部であって、アプリケーションの実行に必要なランタイムにあたる(Java Runtime Environment)には含まれません。アプリケーションを実行するコンテナには当然JREだけを入れおきたいわけですが、そうしてしまうと上記のツール群は利用できないということです。

ここでエフェメラルコンテナの登場ということになります。JVMアプリのコンテナ(JREを使って動いている)を含むPodに対して、JDKエフェメラルコンテナを追加して、JDKに同梱されているデバッグツールを使っていこうというわけです。

f:id:charlier_shoe:20191219033112p:plain
エフェメラルコンテナ x JDKデバッグツール

それではやってみましょう…!

エフェメラルコンテナを利用可能なクラスターの作成

(!!!注意: ここから手順は期待通りに動かなかったというオチなので参考程度に御覧ください!!!)

@superbrothersさんの記事ではminikubeを使っていますが、せっかくなので最近覚えたkindを使ってクラスターを作っていきたいと思います。

エフェメラルコンテナは、最新のKubernetes 1.16から追加されたAlphaの機能です。このため、該当するバージョンで、かつ EphemeralContainers というfeature gateを有効にする必要があります。

kindではクラスターの構成をyaml形式の設定ファイルで定義することができ、ここでfeature gateを指定することが可能です。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
kubeadmConfigPatches:
- |
  apiVersion: kubeadm.k8s.io/v1beta2
  kind: ClusterConfiguration
  metadata:
    name: config
  apiServer:
    extraArgs:
      "feature-gates": "EphemeralContainers=true"
  scheduler:
    extraArgs:
      "feature-gates": "EphemeralContainers=true"
  controllerManager:
    extraArgs:
      "feature-gates": "EphemeralContainers=true"
- |
  apiVersion: kubeadm.k8s.io/v1beta2
  kind: InitConfiguration
  metadata:
    name: config
  nodeRegistration:
    kubeletExtraArgs:
      "feature-gates": "EphemeralContainers=true"
nodes:
- role: control-plane
- role: worker

このようなファイルを作成したら、kindでクラスターの作成を行います。

kind create cluster --config kind-config.yaml
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.16.3) 🖼
 ✓ Preparing nodes 📦 
 ✓ Writing configuration 📜 
 … (snip) … 

クラスターが出来上がったら、feature gateが有効になっていることを確認してみます。

$ kubectl get po -n kube-system kube-apiserver-kind-control-plane -o yaml | grep feature-gates
      - --feature-gates=EphemeralContainers=true

これでクラスターは準備OKです。

kubectl-debugプラグインのインストール

kubectlでエフェメラルコンテナを追加するには、 kubectl debug サブコマンドが必要ですが、これを利用にするために、kubectl-debugプラグインを導入します。

linux_amd64環境での実行例です。環境に合わせて読み替えをお願いします)

$ cd $(mktemp -d)
$ curl -LO https://github.com/verb/kubectl-debug/releases/download/v0.1.2/kubectl-debug_linux_amd64.tar.gz
$ tar xvzf kubectl-debug_linux_amd64.tar.gz
$ sudo mv kubectl-debug /usr/local/bin/
$ kubectl debug -h

JVMアプリのPodを動かす

サンプルのJVMアプリのPodを動かしておきます。

この記事のケースではOpenJDKのJREで動いているアプリであればなんでもOKです。例えば、以下のようなmanifestを作ります。

apiVersion: v1
kind: Pod
metadata:
  name: cowweb
spec:
  shareProcessNamespace: true
  containers:
  - name: cowweb
    image: registry.hub.docker.com/hhayakaw/cowweb:v2.1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 8080

shareProcessNamespace: true となっているところが大切なポイントの1つで、これによってPod内のコンテナがプロセス空間を共有することができます。つまり ps コマンドなどで、Pod内の他のコンテナで動いているプロセスが見えることになります。

それではこのmanifestをクラスターに適用します。

$ kubectl apply -f cowweb.yaml
$ kubectl get pod
$ NAME     READY   STATUS    RESTARTS   AGE
$ cowweb   1/1     Running   0          9s

Podにエフェメラルコンテナを追加する

それでは、エフェメラルコンテナとしてJDKのコンテナをPodに追加します…。と言いたいところなのですが、公式のOpenJDKのイメージはENTRYPOINTがjShellというツールになっていて、通常のシェルではありません。 今回のような使い方では通常のシェルからデバッグツールを実行したいので、以下のようなENTRYPOINTだけを書き換えたコンテナを作って、あらかじめレジストリにPushしておく必要がありました。

FROM openjdk:11-jdk-slim

ENTRYPOINT ["/bin/sh"]

それではいよいよ、Podにエフェメラルコンテナを追加します。 -m は追加するエフェメラルコンテナのイメージ名(この前の手順でPushしたものを指定)、 -c はコンテナの name を指定するオプションです。

kubectl debug cowweb -m hhayakaw/jdk11-debug -c jdebug

Podの内容を見てみると、エフェメラルコンテナが追加されていることがわかります。

kubectl describe pod cowweb | grep -A 5 Ephemeral

このエフェメラルコンテナに kubectl attach します。

kubectl attach -it cowweb -c jdebug

これでエフェメラルコンテナ(OpenJDK 11が入っている)のシェルプロンプトにアクセスできました。

デバッグツールを使う…!(うまく行かない)

いよいよ、デバッグツールを使うときが来ました。

Podには shareProcessNamespace: true がつけてあるので jps を打てばアプリのJVMのプロセスが見えるはず。手始めに jps からの jcmdJVMの起動フラグの一覧でも見てみるか、と思うわけですが…。

# jps
29 Jps

jpsそのもののプロセスしか見えない…?あれー、なんで?

...

調べてみたところ、jpsを利用するには ptrace システムコールが必要になるようで、エフェメラルコンテナにこれを実行できる権限をつけないといけません。しかし、 kubectl debug コマンドにはそんな権限を追加するオプションはない…。

仕方ないので、JSONsecurityContextSYS_PTRACE が有効になるように記述したエフェメラルコンテナを作って、 kubectl replace でPodに追加してみます。 kubectl replaceエフェメラルコンテナを追加する方法は、公式ドキュメントにサンプルがあったので、それに securityContext の記述を追加してあげる感じです。

まずはJSON

{
    "apiVersion": "v1",
    "kind": "EphemeralContainers",
    "metadata": {
        "name": "cowweb"
    },
    "ephemeralContainers": [
        {
            "name": "jdebug",
            "image": "openjdk:11-jdk-slim",
            "imagePullPolicy": "IfNotPresent",
            "command": [
                "/bin/sh"
            ],
            "stdin": true,
            "tty": true,
            "terminationMessagePolicy": "File",
            "securityContext": {
                "capabilities": {
                    "add": [
                        "SYS_PTRACE"
                    ]
                }
            }
        }
    ]
}

エフェメラルコンテナ追加!

kubectl replace --raw /api/v1/namespaces/default/pods/cowweb/ephemeralcontainers -f jdebug.yaml

え…。

The Pod "cowweb" is invalid: spec.ephemeralContainers[0].securityContext: Forbidden: cannot be set for an Ephemeral Container

securityContext: Forbidden 。どうやらエフェメラルコンテナではやろうとしている権限追加は制限されているようです。

残念ながら、JVMデバッグツールは、エフェメラルコンテナからでは使えないという結論に…。

3 . KubernetesJDKデバッグツールを利用する現状可能な方法

ということで、似たようなことを現状のKubernetesの機能でやろうとするとどうなるかというと、エフェメラルコンテナの代わりにサイドカーコンテナを使うことが考えられます。

つまり、下のように、JDK入りのコンテナをあらかじめPodに入れておくというやり方です。

apiVersion: v1
kind: Pod
metadata:
  name: cowweb
spec:
  shareProcessNamespace: true
  containers:
  - name: cowweb
    image: registry.hub.docker.com/hhayakaw/cowweb:v2.1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 8080
  - name: jdebug
    image: openjdk:11-jdk-slim
    imagePullPolicy: IfNotPresent
    command: ['/bin/sh']
    stdin: true
    tty: true
    terminationMessagePolicy: File
    securityContext:
      capabilities:
        add:
        - SYS_PTRACE

このようなPodを作った上でサイドカーコンテナ(jdebug)にアタッチすれば、JVMツールを利用することが可能です。

kubectl attach -it cowweb -c jdebug

以下のように、 jpsJVMアプリのプロセスIDを確認して、 jcmdJVMのフラグを表示する、といったことができます。

# jps
7 cowweb.jar  <-- アプリのプロセスが見えている
29 Jps
# jcmd 7 VM.flags                                
7:
-XX:CICompilerCount=2 -XX:InitialHeapSize=264241152 -XX:MaxHeapSize=4204789760 -XX:MaxNewSize=1401552896 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=88080384 -XX:NonNMethodCodeHeapSize=5825164 -XX:NonProfiledCodeHeapSize=122916538 -XX:OldSize=176160768 -XX:ProfiledCodeHeapSize=122916538 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC

しかし、サイドカーコンテナをデバッグのために使う場合、必要なときだけこのコンテナを起動するということはできません。 Kubernetes的には本体(ここではJVMアプリ)のコンテナもサイドカーコンテナも取り扱いに区別はなく、どちらかのコンテナが動作していないPodは異常な状態とみなされて、再起動がかけられてしまいます。

このため、JDKの入ったコンテナを、普段は使わないのに上げっぱなしにしておかなければいけないわけです。もちろん、コンテナが動いていると言ってもJVMまで起動しているわけではないので、リソース(メモリやCPU)の消費は大したことはありません。しかしPodの中に余計なものがあるのは好ましくありませんし、権限の強いコンテナが置きっぱなしなのはセキュリティ的にもいいことではないでしょう。

4 . まとめ - Podとコンテナのライフサイクルについて

サイドカーコンテナを使えばアプリのコンテナに対してJVMツールを実行することが可能ですが、本体のコンテナと起動・停止のライフサイクルが同じになるように強制されてしまうため、デバッグという用途だけを考えると理想的ではありません。エフェメラルコンテナは、Podに後から追加できるなど、デバッグに適した本体のコンテナとは別のライフサイクルを持っていますが、JVMデバッグツールを実行するには制限がありました。

いまのところ、JVMデバッグツールという限られた用途に関していうと、エフェメラルコンテナは機能が足りていないということになりそうです。

ただ、今回の検証は残念な結果に終わってしまったものの、エフェメラルコンテナの特徴である「本体のコンテナと別のライフサイクルを持つ追加コンテナ」というコンセプトには、大きな可能性を感じました。

例えば、1)アプリ本体のコンテナに対して、起動初期に暖機運転のトラフィックを送るだけのコンテナを設けたり、2)ログエージェントのような、それが死んでもPodを落とすまではしてほしくない、といったケースは現実に存在すると思います。これらをカバーするには、本体のコンテナとは独立したライフサイクルが求められてくると思うのです。

こういった機能が徐々にサポートされて、Kubernetesがより便利に発展していってくれるといいですね。自分もできる働きかけしていければと思います。


深夜3:00を回ってだいぶ取り留めがなくなってきたので、このへんで…。

皆さん良いお年をお迎えください。メリー・クリスマス!


  1. 他にも同じものを含むディストリビューションもあるかもしれません

Helidonで作ったマイクロサービスをPrometheus Operatorでサクッとモニタリング

Helidonとは

HelidonはマイクロサービスなWeb APIを実装することを想定したJavaフレームワークです。以下のような特徴があります。

  • 割と軽い実装で、パフォーマンスに優れる
  • MicroProfileの実装の一つ
  • PrometheusやZipkinの形式のメトリック/トレーシングの情報を簡単に取得できる

f:id:charlier_shoe:20190207113902j:plain

このエントリーでやること

Helidonで作ったマイクロサービスをPrometheus Operatorでサクッとモニタリングしてみます。だいたい以下のような流れになります。

  1. Kubernetesクラスター上に、PrometheusとPrometheus Operatorをデプロイする
  2. Helidonでアプリを作成し、Kubernetesクラスターにデプロイする
  3. Prometheusでアプリのアクセスカウンターをモニタリングする

では順に行きたいと思います。

1. Kubernetesクラスター上に、PrometheusとPrometheus Operatorをデプロイする

CRDやらなんやらをKubernetesクラスターに作ることになるので、kubectlコマンドを実行するアカウントに必要な権限をつけておきます。 今回はKubernetesの環境として、OKE(Oracle Container Engine for Kubernetes)を利用します。この場合、以下のようなコマンドを実行します(cluster-adminロールをつけて楽しちゃってます)。

kubectl create clusterrolebinding [Cluster Role Bindingオブジェクトの名前] --clusterrole=cluster-admin --user=[kubectlの実行アカウントのOCID]

詳細はOKEのドキュメントを参照ください。

次に、適当なディレクトリにPrometheus Operatorをcloneします。

git clone -b v0.28.0 https://github.com/coreos/prometheus-operator.git
cd prometheus-operator

諸々をクラスターにデプロイします。 (2019/02/06時点でkube-prometheusはExperimentalだそうなので、今後同じ方法が使えるかは未知数です)

kubectl apply -f contrib/kube-prometheus/manifests/

Podが上がりきったのを確認したら、Prometheusのダッシュボードにアクセスします。PrometheusにのダッシュボードにつながるServiceはClusterIPで作られているので、port forwardを使います。

kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090

上のコマンドを実行したら、ブラウザでhttp://localhost:9090/にアクセスします。以下のような感じでダッシュボードが表示されればOKです。

f:id:charlier_shoe:20190207013510p:plain

2. Helidonでアプリを作成し、Kubernetesクラスターにデプロイする

HelidonのアプリはクイックスタートにあるHello World的なアプリを使います。

まず、以下のコマンドを実行します。

mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion=0.11.0 \
    -DgroupId=io.helidon.examples \
    -DartifactId=quickstart-se \
    -Dpackage=io.helidon.examples.quickstart.se
cd quickstart-se

これでHelidonのプロジェクトの雛形が作成されますが、この雛形にはすでにHello World的なAPIが実装されています。

この雛形に、カスタムメトリックとしてアクセスカウンターを追加してみます。

やり方はHelidonのドキュメントのMetrics supportに従えばOKですが、quickstartを使っている場合、必要な手順は一箇所だけ、GreetingServiceにアクセスカウンターを書き足す作業です。

vim src/main/java/io/helidon/examples/quickstart/se/GreetService.java

以下、diff形式で差分を記します。行頭に + が書かれているところを追記すればOKです。

@@ -25,6 +25,10 @@
 import io.helidon.webserver.ServerResponse;
 import io.helidon.webserver.Service;
 
+import io.helidon.metrics.RegistryFactory;
+import org.eclipse.microprofile.metrics.Counter;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+
 /**
  * A simple service to greet you. Examples:
  *
@@ -42,6 +46,10 @@
 
 public class GreetService implements Service {
 
+    private final MetricRegistry registry = RegistryFactory.getRegistryFactory().get()
+        .getRegistry(MetricRegistry.Type.APPLICATION); 
+    private final Counter accessCtr = registry.counter("accessctr"); 
+
     /**
      * The config value for the key {@code greeting}.
      */
@@ -58,6 +66,7 @@
     @Override
     public void update(Routing.Rules rules) {
         rules
+            .any(this::countAccess)
             .get("/", this::getDefaultMessageHandler)
             .get("/{name}", this::getMessageHandler)
             .put("/greeting/{greeting}", this::updateGreetingHandler);
@@ -108,4 +117,9 @@
         response.send(returnObject);
     }
 
+    private void countAccess(ServerRequest request, ServerResponse response) {
+            accessCtr.inc(); 
+            request.next();
+    }
+
 }

Kubernetesにデプロイするためのmanifestファイルの雛形もあります。 後の手順でアプリをコンテナ化しますが、このときのイメージ名に合うように、manifestで指定するイメージ名を修正しておきます。

vim src/main/k8s/app.yaml

イメージ名をしている箇所で、元のイメージ名の頭に "[Docker Hubのアカウント名]/" を追記します。以下はhhiroshellというアカウント名の場合です。

@@ -43,7 +43,7 @@
     spec:
       containers:
       - name: ${project.artifactId}
-        image: ${project.artifactId}
+        image: hhiroshell/${project.artifactId}
         imagePullPolicy: IfNotPresent
         ports:
         - containerPort: 8080

コードの編集が終わったら、アプリケーションをビルドしてローカルで実行してみます。まずはビルドから。

mvn package
[INFO] Scanning for projects...
...(中略)...
[INFO]
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ quickstart-se ---
[INFO] Building jar: /mnt/c/Users/hhiroshell/Development/devsumi/quickstart-se/target/quickstart-se.jar [INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  34.004 s
[INFO] Finished at: 2019-02-06T14:14:55Z
[INFO] ------------------------------------------------------------------------

上記の様に BUILD SUCCESS で終わっていればOKです。次に、アプリを起動します。

java -jar target/quickstart-se.jar

何回かgreetingのAPIにアクセスして、アクセスカウンターをカウントアップさせておきます。

curl http://localhost:8080/greet

Helidonは、Prometheusからメトリックを取得するためのEndpointをデフォルトで持っています。ですので、直接 /metrics/ というパスにアクセスするとメトリックを取得できます。新たにExporterを用意する必要はありません。

先程追加したアクセスカウンターは、application:accessctr という名前でメトリックを提供しています。エンドポイントにアクセスして、このアクセスカウンターの値を見てみます。

curl -s -H 'Accept: text/plain' -X GET http://localhost:8080/metrics/ | grep accessctr
# TYPE application:accessctr counter
# HELP application:accessctr null
application:accessctr 5

この様になれば、仕込んでおいたアクセスカウンターのメトリックが取得できていることになります。Prometheusの形式で値が返ってきていることが分かります。

それでは、このアプリをKubernetesにデプロイします。雛形からDockerfileやKubernetesのmanifestが生成済みなので、それを利用していきます。

まずはコンテナのビルド。

docker build -t [Docker Hubのアカウント名]/quickstart-se target

具体的には、例えばこのようなコマンドになります。

docker build -t hhiroshell/quickstart-se target

次にコンテナレジストリにプッシュ。Docker Hubを使っているので、ログインが求められたらDocker Hubのユーザー名とパスワードを入力してください。

docker push [Docker Hubのアカウント名]/quickstart-se

具体的には、例えばこのようなコマンドになります。

docker push hhiroshell/quickstart-se

続いて、kubernetesにデプロイします。

kubectl apply -f target/app.yaml

雛形から生成したmanifestだと、NodePortタイプのServiceが作られている状態です。クラスター上のアプリにアクセスするため、まずはNodeのIPアドレスと、Serviceが公開されているPort番号を確認します。

kubectl get nodes -o wide
NAME            STATUS   ROLES   AGE   VERSION   INTERNAL-IP   EXTERNAL-IP     OS-IMAGE                  KERNEL-VERSION                   CONTAINER-RUNTIME
132.145.27.98   Ready    node    2h    v1.11.5   10.0.10.2     132.145.27.98   Oracle Linux Server 7.5   4.14.35-1818.2.1.el7uek.x86_64   docker://18.3.1

この結果から、EXTERNAL-IP の値を使います(Nodeが複数表示される場合どのNodeでもOK)。

次にPort番号です。

kubectl get service quickstart-se
NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
quickstart-se   NodePort   10.96.255.64   <none>        8080:31621/TCP   7m

PORT(S)の : の右側にある値を使います。上の例では31621です。

それでは、Kubernetesクラスター上のアプリケーションにアクセスしてみます。

curl "http://[上で確認したIP]:[上で確認したPort番号]/greet"

何度かアクセスを繰り返して、アクセスカウンターの値を増やしておいてください。

これでHelidonのアプリのビルドとデプロイが完了しました。

3. Prometheusでアプリのアクセスカウンターをモニタリングする

Prometheus Operatorを使ってPrometheusを利用する場合、監視対象を追加するにはServiceMonitorというカスタムリソースを使います。これは、KubernetesのServiceがルーティング対象とするPodを、自動的にメトリック収集対象とするものです。

今回は、以下のようなカスタムリソースを、quickstart-service-monitor.yamlというファイル名で作成します。

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: quickstart-service-monitor
  labels:
    app: quickstart-se
spec:
  selector:
    matchLabels:
      app: quickstart-se
  endpoints:
  - port: http

このServiceMonitorは、Label Selectorとして app: quickstart-se と指定しています。こうすると、同じlabelを持ったServiceがルーティング対象とするPodに対して、メトリックを取得しに行くするように設定されます。

多くの場合、Prometheusがメトリックを取得しに行くEndpointにはExporterを用意しますが、HelidonはこのEndpointを予め持っており、そこから直接メトリックを得ることができます。

ではこのSerivceMonitorをクラスターに適用します。

kubectl apply -f quickstart-service-monitor.yaml

Prometeusの設定はこれだけでOKです。Operator便利ですね。

では、ブラウザでhttp://localhost:9090/にアクセスして、Prometheusのダッシュボードを表示します。

port-forwardが切れていたら、以下を実行してから上記のURLにアクセスします。

kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090

Expressionに"application:accessctr"と入力してExecuteをクリックすると、アクセスカウンターの値が取得できていることがわかります(メトリックが収集させるまでタイムラグがあるので、何も表示されなかった場合は少し時間を置いてください)。

f:id:charlier_shoe:20190207013607p:plain

次に、アプリをスケールアウトした場合も自動的に監視対象に追加されることを確認してみます。

まず、以下のコマンドでアプリのPod数を3に増やします。

kubectl scale deployment quickstart-se --replicas=3

アクセス数を追加でカウントアップさせるため、アプリへのアクセスを何度か行っておきます。
(↓を何度か実行)

curl "http://[上で確認したIP]:[上で確認したPort番号]/greet"

Prometheusのダッシュボードでアクセスカウンターのメトリックを観察すると、Pod毎に値が取得できていることがわかります。

f:id:charlier_shoe:20190207013632p:plain

まとめ

Helidonで作成したアプリのカスタムメトリックを、Prometheus Operatorを使ってモニタリングすることができました。

HelidonはExporterいらずでモニタリングできて便利です。また、Prometheus Operatorを使うとカスタムリソースを使うことで簡単にメトリックの収集対象を設定できます。

本エントリーは以上です!

Werckerのパイプライン内でDockerfileからコンテナをビルドする方法

Werckerはパイプラインの実行環境となっているコンテナからコンテナイメージを作るのがデフォルトの動きですが、なるべく軽量なコンテナにしたいなど、用途に最適化されたコンテナを作りたいという場合もあります。

そんなときは、コードリポジトリにDockerfileを入れておいて、それを使ってビルドすればOKです。

wercker.ymlの書き方は以下のような感じです。

box: alpine:3.6

build:
  steps:
    - internal/docker-build:
        name: build custom container.
        dockerfile: Dockerfile
        image-name: custom-container

    - internal/docker-push:
        name: push custom container.
        image-name: custom-container
        username: $REGISTRY_USERNAME
        password: $REGISTRY_PASSWORD
        repository: hhiroshell/custom-container
        tag: 1.0

internal/docker-build で所望のDockerのコンテキストでコンテナをビルドできます。 dockerfile: に設定しているのはDockerfileまでのパスです。パイプラインの実行中は、コードリポジトリのrootに当たるディレクトリがカレントになっています。上の例はコードリポジトリのrootの直下にDockerfileを置いておき、それを参照していることになります。

image-name:は後続のStepでイメージを操作したいときに、イメージを特定するための名前です。この例だと docker-push StepでビルドしたイメージをPushしています。

その他いろいろな設定が可能ですが、詳細は公式ドキュメントに詳しいです。

Oracle Cloud InfrastructureのCLIをvirtualenv環境内にインストールする

Oracle Cloud Infrastructure(以下、OCI)のCLIPythonで実装されています。Pythonのvirtualenvを使って、OCI CLI専用の環境を切ってインストールしておけば、Pythonにありがちな依存モジュールの問題に悩まされずに済みますので、このエントリーではその手順を書きます。1

ちなみに、本エントリーとは直接関係ありませんが、CLIの他にOCIのAnsibleモジュールも使いたい場合には、より一層virtualenvの利用をおすすめしたいです。OCIのAnsibleモジュールはOCIのPython SDKというものに依存しているのですが、自分の環境では依存モジュールの問題にはまって動かすまで苦労したので…。

前提条件

こんな環境でやりました。

手順

pipをインストール

sudo apt update
sudo apt install python3-pip

pipでvirtualenvをインストール

pip3 install virtualenv

OCI CLI用のvirtualenv環境を作成。virtualenvは新たにディレクトリを切って、その配下で依存パッケージが管理される仕組みです。ここでは、~/.pyenv_oci-cliという名前のディレクトリにしています。

python3 -m virtualenv ~/.pyenv_oci-cli --no-site-packages

上で作成したvirtualenvを有効化

source ~/.pyenv_oci-cli/bin/activate

pipでvirtualenv環境にOCI CLIをインストール

pip install oci-cli

動作確認。以下のコマンドでヘルプが表示されればOKです。

oci -h

新たにシェルを立ち上げたら、都度virtualenvの有効化を行う必要があります。頻繁にOCI CLIを使うのであれば、.bashrcとか.zshrcに該当コマンドを書いておくと良いかと思います。

source ~/.pyenv_oci-cli/bin/activate

OCI CLIのインストールはこれで完了です。CLIの環境設定の手順はvirtualenvを使っていてもいなくても同じなので、「Oracle Cloud Infrastructure CLIを設定してみた」などを参考にして実施してください。


  1. virtualenvを使わない標準的なインストール手順は、こちらこちらを参照ください。

Ubuntu 18.04 bionicにOpenJDK 11をインストールする

2018/12/27現在、Ubuntu 18.04でaptでOpenJDKをインストールするとjdk10がインストールされるようになっています。1

いろいろと対処法はあるようなのですが、せっかくなので本家のOpenJDKを落として手動でインストールしとこうということで、その手順を残しておきます。

手順

OpenJDKのダウンロードサイトに行って、ビルド済みのアーカイブを取得するためのURLを確認します。

Ubuntu 18.04の場合は、Linux/x64のURLを確認して適当に控えて置きます。

以下、順次以下のコマンドを実行します。URLやファイル名などは本稿執筆時点のものなので、作業を行う時点のものを確認して適宜置き換えてください。

アーカイブのダウンロード

wget https://downlo8ad.java.net/java/GA/jdk11/13/GPL/openjdk-11.0.1_linux-x64_bin.tar.gz -O ./openjdk-11.0.1_linux-x64_bin.tar.gz

解凍

sudo mkdir /usr/lib/jvm
sudo tar xfvz ./openjdk-11.0.1_linux-x64_bin.tar.gz --directory /usr/lib/jvm

お掃除

rm openjdk-11.0.1_linux-x64_bin.tar.gz

コマンドにシンボリックリンクを貼っておく

javajavacくらいはやっておくと便利かと。

sudo ln -s /usr/lib/jvm/jdk-11.0.1/bin/java /usr/bin/java
sudo ln -s /usr/lib/jvm/jdk-11.0.1/bin/javac /usr/bin/javac

動作確認

こんな結果になればおk

java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)

以上です。

OKE、OCIRを使うときに権限制御するための設定まとめ

このエントリーはOracle Cloud アドベントカレンダーその2の22日目です。

今回はOracleが提供するコンテナ関連のクラウドサービスである、OKE、OCIRを使う上での、準備に当たる作業の話を書きたいと思います。

これらを使うには、Policyと呼ばれるアクセス権限の設定を予めしておく必要があります。 このエントリーでは、それら必要な権限周りの設定を整理したいと思います(他のエントリーではそのエントリーで必要なものだけ書いているので)。

管理者権限を打ち込めばすぐに使えるようにはなりますが、ちゃんと権限を分けたいときにはこの情報を算用してください。

OKEを使うために必要なポリシー

注: in tenancyin compartment [コンパートメント名]のようにすると、権限の及ぶ範囲をコンパート名に絞る事ができます。

  • OKEのPaaSに与える権限OKEを使つときは必須

    • Statement:
      • Allow service OKE to manage all-resources in tenancy
    • 説明:
      • OKEのPaaSに権限を与えるための特殊なポリシー。OKEを使うときはこれを必ず作る。これがないと始まらない
  • OKEクラスターとNode Poolを操作する権限

    • Statement:
      • Allow group [グループ名] to manage cluster-family in tenancy
      • Allow group [グループ名] to inspect vcns in tenancy
      • Allow group [グループ名] to inspect subnets in tenancy
    • 説明:
      • OKEのクラスターとNode Poodを操作する権限(3つセット)。3つのうち下のの2つは、クラスターが依存する下回りのネットワークを参照するためのもの。
      • このセットには下回りのネットワークをいじる権限は含まれない(依存するネットワークは作成済みの前提)
  • OKEクラスターとNode Poolを操作する権限 + ネットワークの操作権限

    • Statement:
      • Allow group [グループ名] to manage cluster-family in tenancy
      • Allow group [グループ名] to manage virtual-network-family in tenancy
    • 説明:
      • OKEをネットワークも含めて作ったり操作したいときに必要な権限。2つ目の方は下回りのネットワークを操作するためのもの
      • OKE作成のダイアログでquick clusterを使うときにはこの権限が必要
  • OKEクラスターを参照する権限

    • Statement:
      • Allow group [グループ名] to inspect clusters in tenancy
    • 説明:
  • OKEクラスターのNode Poolを操作する権限

    • Statement:
      • Allow group [グループ名] to use cluster-node-pools in tenancy
    • 説明:
      • 作成済みのOKEクラスターのNode Poolを追加、削除、アップデート(構成変更など)する権限
  • OKEクラスターへの操作の監査情報を参照する権限

    • Statement:
      • Allow group [グループ名] to read cluster-work-requests in tenancy
    • 説明:
      • OKEクラスターへの操作の監査情報を参照する権限

OCIRを使うために必要なポリシー

以下は全てを網羅したものではありませんが、ほとんどのケースはこれらがあれば大丈夫かと思います。

  • リポジトリの閲覧権限

    • Statement:
      • Allow group [グループ名] to inspect repos in tenancy
    • 説明:
  • イメージの取得権限(全てのリポジトリ

    • Statement:
      • Allow group [グループ名] to read repos in tenancy
    • 説明:
  • イメージの取得権限(リポジトリを限定)

    • Statement:
      • Allow group [グループ名] to read repos in tenancy where all { target.repo.name=/[リポジトリ名]/ }
    • 説明:
      • OCIRの指定されたリポジトリのイメージをpullする権限。/example-app*/のように正規表現を使うこともできる模様
  • イメージの登録権限(全てのリポジトリ

    • Statement:
      • Allow group [グループ名] to use repos in tenancy
    • 説明:
  • イメージの登録権限(リポジトリが存在しない場合に新たに作る権限も追加)

    • Statement:
      • Allow group [グループ名] to manage repos in tenancy where ANY {request.permission = 'REPOSITORY_CREATE', request.permission = 'REPOSITORY_UPDATE'}
    • 説明:
  • OCIRへのフルアクセス

    • Statement:
      • Allow group [グループ名] to manage repos in tenancy
    • 説明:

Oracle Container Engine for KubernetesでLoadBlancerタイプのServiceを使う

このエントリーはOracle Cloud アドベントカレンダーその2の21日目です。

今回は、OKE(Oracle Container Engine for Kubernetes)で作ったKubernetesクラスターでLoadBalancerタイプのServiceを使う手順を紹介します。

0. 前提条件

  • OKEのクラスターをプロビジョング済みであること。手順はこちらをご参照ください。

1. 手順

ほとんどのマネージドKubernetesサービスは、自身のクラウド環境で提供されているロードバランサーのサービスと連携してServiceオブジェクトを作成する機能を提供しており、OKEも同様です。

1.1. まずはデフォルト設定で

まずは、my-nginx.yamlというファイル名で以下のような内容のファイルを作成します。

apiVersion: v1
kind: Service
metadata:
  name: my-nginx-svc
  labels:
    app: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

これはなんの変哲もない、nginxのコンテナを公開するだけのmanifestファイルです。

これをOKEクラスターに適用してみます。

kubectl apply -f my-nginx.yaml

10-20秒程度おいてから以下のコマンドを実行し、Serviceオブジェクトの内容を確認してみます。

k get svc
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1       <none>           443/TCP        15m
my-nginx-svc   LoadBalancer   10.96.122.251   129.213.78.183   80:32250/TCP   30s

my-nginx-svcというServiceが129.213.78.183というIPアドレスで公開されていることがわかります(IPアドレスは環境や実行の度に変わります)。

ブラウザでこのアドレスhttp://129.213.78.183/アクセスすると、OKEクラスターで動いているnginxからの応答を得ることができます。

f:id:charlier_shoe:20181221162747p:plain

Oracle Cloud Infrastructureのコンソールでロードバランサーの方を確認してみると、ランダムに生成されたと思しき名前でロードバランサーが作られていることがわかります。

f:id:charlier_shoe:20181221162759p:plain

IPアドレスは先程のアドレス一致します。また、シェイプは[100]Mbpsになっていると思います。

Oracle Cloud Infrastructureでは400[Mbps]と8000[Mbps]も提供しています。これらを使いたい場合には、Serviceオブジェクトの記述にannotationを追加します。

  • 400[Mbps]
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-svc
  labels:
    app: nginx
  annotations:                                                  <--ココ
    service.beta.kubernetes.io/oci-load-balancer-shape: 400Mbps <--ココ
...(以下略)...
  • 8000[Mbps]
apiVersion: v1
kind: Service
metadata:
  name: my-nginx-svc
  labels:
    app: nginx
  annotations:                                                   <--ココ
    service.beta.kubernetes.io/oci-load-balancer-shape: 8000Mbps <--ココ
...(以下略)...

例えば400[Mbpx]でやってみます。ファイルを変更後、以下のコマンドを実行します。

kubectl delete -f my-nginx.yaml
kubectl create -f my-nginx.yaml

今度は400[Mbps]のロードバランサーができました!(ロードバランサーの作り直しになるので、IPアドレスも変わっています)

f:id:charlier_shoe:20181221162831p:plain

本エントリーでご紹介する内容は以上です。

シェイプを変更する以外にもSSLを終端するように設定したりすることも可能です。詳しくは公式のドキュメントを参照してください。