Kubernetesのdashboardをingressから表示する

Kubernetesの状態が知りたいなと思い調べたところ、公式のWebUIであるdashboardをみつけました。
README通りにやれば簡単にインストールできますが、proxy経由でしか表示させる方法が書いてなく不便だったので、ingressを使用して表示させる方法を書きます。

証明書が入ったsecretの作成

dashboardは証明書がないとアクセスできない設定になっているので、まずは証明書を作成します。

dashboardのアクセスにドメインを使用する場合はLet's Encryptで簡単に証明書が取得できるので、取得しましょう。
Let's Encryptの使い方は他のサイトに譲ります。

ドメインがない場合は自己証明書を用意します。
このやり方はdashboardwikiに書いてあるので、それ通りにやればOKです。

Certificate management · kubernetes/dashboard Wiki · GitHub

証明書が準備できたらsecretの作成に入ります。

$ kubectl create secret tls kubernetes-dashboard-certs --key dashboard.key--cert dashboard.crt -n kube-system

dashboardはkube-systemというnamespaceに作成されるので、このnamespaceにsecretを作成する必要があります。

dashboardのインストール

dashboardのインストールは公式にある通りにapplyするだけです。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

ingressの設定

基本的には通常と同じようにingressを設定すれば問題ありませんが、1つだけ注意しなければいけないことがあります。

ingress-nginxの場合は、namespaceごとにingressを作成する必要があります。
そのため、今回はkube-systemというnamespaceにingressを作成します。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/secure-backends: "true"
  name: dashboard-ingress
  namespace: kube-system
spec:
  rules:
    - host: "sample.dash.jp"
      http:
        paths:
          - path: /
            backend:
              serviceName: kubernetes-dashboard
              servicePort: 443
  tls:
    - hosts:
      - "sample.dash.jp"
      secretName: kubernetes-dashboard-certs

これで指定したドメインにアクセスするとdashboardのUIが表示されます。

GKEにPrometheusを導入する

GKEにはPrometheusを導入するドキュメントが無いんですよね・・・
IstioやSpinnakerのドキュメントはあるのに、もっと需要がありそうなPrometheusのドキュメントが無いのはつらいです。

そんなわけで、自分が導入したときの手順を記録しておきます。

環境

GKE 1.9.7-gke.0

手順

結構やることが多いので簡単に内容をまとめます。

  1. Namespaceを作成
  2. ClusterRoleを作成
  3. 永続ディスクの作成
  4. 設定ファイルを登録
  5. Prometheusを起動

1. Namespaceを作成

Prometheusやその後に導入することになるGrafanaをメインのPodと同じNamespaceに入れてしまうと、稼働しているサービスのPodと監視用のPodが混ざってしまいあまり嬉しくないので、監視に使うPodは別のNamespaceに入れることにします。

今回はmonitoringというNamespaceを作成します。

$ kubectl create namespace monitoring

2. ClusterRoleを作成

PrometheusがKubernetes APIからメトリクスを取得するために先程作成したNamespaceにアクセス権を付与します。

ClusterRoleで必要な権限を指定して、ClusterRoleBindingで権限を与えたいNamespaceを指定する形になっています。

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: prometheus
rules:
- apiGroups: [""]
  resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
  verbs: ["get", "list", "watch"]
- apiGroups:
  - extensions
  resources:
  - ingresses
  verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
- kind: ServiceAccount
  name: default
  namespace: monitoring

3. 永続ディスクの作成

永続ディスクについては別の記事で解説しているので、それを見てください。

今回使ったyamlはこれです。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: prometheus-pv
  namespace: monitoring
  labels:
    app: prometheus-pv
spec:
  capacity:
    storage: 200Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  gcePersistentDisk:
    pdName: prometheus-disc
    fsType: ext4
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: prometheus-pvc
  namespace: monitoring
  labels:
    app: prometheus-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  resources:
    requests:
      storage: 200Gi
  selector:
    matchLabels:
      app: prometheus-pv

4. 設定ファイルを登録

Prometheusの設定ファイルをConfig Mapを経由して登録します。

このファイルにはKubernetesで実行されているPodやServiceを動的に見つけるための設定が書かれています。
この設定によって、可変するPodなどを余すことなく監視することができます。

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-server-conf
  labels:
    name: prometheus-server-conf
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval:     15s
      evaluation_interval: 15s
    scrape_configs:
    - job_name: 'kubernetes-apiservers'
      kubernetes_sd_configs:
      - role: endpoints
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      relabel_configs:
      - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
        action: keep
        regex: default;kubernetes;https

    - job_name: 'kubernetes-nodes'
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - target_label: __address__
        replacement: kubernetes.default.svc:443
      - source_labels: [__meta_kubernetes_node_name]
        regex: (.+)
        target_label: __metrics_path__
        replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

    - job_name: 'kubernetes-cadvisor'
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - target_label: __address__
        replacement: kubernetes.default.svc:443
      - source_labels: [__meta_kubernetes_node_name]
        regex: (.+)
        target_label: __metrics_path__
        replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

    - job_name: 'kubernetes-service-endpoints'
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_service_name]
        action: replace
        target_label: kubernetes_name

    - job_name: 'kubernetes-services'
      metrics_path: /probe
      params:
        module: [http_2xx]
      kubernetes_sd_configs:
      - role: service
      relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - target_label: __address__
        replacement: blackbox-exporter.example.com:9115
      - source_labels: [__param_target]
        target_label: instance
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_service_name]
        target_label: kubernetes_name

    - job_name: 'kubernetes-ingresses'
      metrics_path: /probe
      params:
        module: [http_2xx]
      kubernetes_sd_configs:
      - role: ingress
      relabel_configs:
      - source_labels: [__meta_kubernetes_ingress_scheme,__address__,__meta_kubernetes_ingress_path]
        regex: (.+);(.+);(.+)
        replacement: ${1}://${2}${3}
        target_label: __param_target
      - target_label: __address__
        replacement: blackbox-exporter.example.com:9115
      - source_labels: [__param_target]
        target_label: instance
      - action: labelmap
        regex: __meta_kubernetes_ingress_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_ingress_name]
        target_label: kubernetes_name

    - job_name: 'kubernetes-pods'
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_pod_name]
        action: replace
        target_label: kubernetes_pod_name

5. Prometheusを起動

いよいよPrometheus本体の導入に入ります。

やることは単純で、これまでに作成した永続ディスクとConfig MapをPrometheusの公式イメージにマウントするだけです。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: prometheus-deployment
  namespace: monitoring
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: prometheus-server
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:v2.1.0
          args:
            - "--config.file=/etc/prometheus/prometheus.yml"
            - "--storage.tsdb.path=/prometheus/"
          ports:
            - containerPort: 9090
          volumeMounts:
            - name: prometheus-config-volume
              mountPath: /etc/prometheus/
            - name: prometheus-storage-volume
              mountPath: /prometheus/
      volumes:
        - name: prometheus-config-volume
          configMap:
            defaultMode: 420
            name: prometheus-server-conf
        - name: prometheus-storage-volume
          persistentVolumeClaim:
            claimName: prometheus-pvc
      securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 1000

これでPrometheusが可動します。
せっかくなので動作確認をしてみましょう。

コンテナをローカルマシンにつなげます。

$ kubectl port-forward prometheus-monitoring-**** 8080:9090

http://localhost:8080でPrometheusに接続できます。

ここまででコンテナのメトリクスが取得できるようになりました。しかし、まだKubernetesのNodeのメトリクスが取得できていません。
次回はNodeのメトリクスも取得できるように拡張していきます。

GKEでPersitentVolumeを使う

KubernetesでPersitentVolumeを使う方法はドキュメントに書いてありますが、使う環境によって永続ディスクが異なるためにスムーズにいかないことがあります。
そのため、今回はGKEでPersitentVolumeを使う方法を紹介します。

環境

GKE 1.9.7-gke.0

永続ディスクの用意

まずはGCPで永続ディスクを作成します。

GUICUIどちらでも大丈夫ですが今回はCUIで作成してみます。

$ gcloud compute --project={your-project} disks create sample-disk --zone=asia-northeast1-b --type=pd-ssd --size=100GB

このようになると思います。

f:id:akaimo3:20180523215724p:plain

これで永続ディスクの作成は完了です。

PersitentVolumeの作成

ここからはKubernetesの設定に入ります。

Podが永続ディスクを使用するためにはPersitentVolumeだけではなく、PersistentVolumeClaimというものも作成しなければいけません。

PersitentVolumeには先程作成した永続ディスクについての設定を記述します。
また、PersistentVolumeClaimを作成することで、PodがPersitentVolumeを見つけることができるようになります。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: sample-pv
  labels:
    app: sample-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  gcePersistentDisk:
    pdName: sample-disk
    fsType: ext4
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sample-pvc
  labels:
    app: sample-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  resources:
    requests:
      storage: 100Gi
  selector:
    matchLabels:
      app: sample-pv

Podにマウントする

最後にPodからアクセスできるようにします。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  ...
  template:
    ...
    spec:
      containers:
        - name: sample
          ...
          volumeMounts:
            - name: sample-volume
              mountPath: /sample
      volumes:
        - name: sample-volume
          persistentVolumeClaim:
            claimName: sample-pvc
      securityContext:
        fsGroup: 2000
        runAsNonRoot: true
        runAsUser: 1000

ポイントはsecurityContextの部分です。
この記述がないと、permissionでエラーになってPodが起動しなくなってしまいます。

以上がGKEでPersitentVolumeを使用する方法です。
PersitentVolumeのような環境に依存する部分は、具体的なコードがあまりないので誰かの参考になれば嬉しいです。

KubernetesでBlue-Green Deploymentしてみる

今回はサービスを本番で運用していくときに欲しくなるBlue-Green DeploymentをKubernetesでやってみます。

TL;DR

  • Serviceのselectorを更新するやり方だと10分程度BlueとGreenがまざる
  • Istioを使用すれば瞬時に100%のトラフィックを切り分けられるのでBlue-Green Deploymentができる

環境

  • GKE 1.9.7-gke.0

2種類のAPI

BlueとGreenを見分けるために、自身の色を返すAPIを用意します。

package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "blue-api")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":80", nil)
}

そして、このAPIが稼働するDeploymentがこれです。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: blue-api
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: color-api
        version: blue
    spec:
      containers:
      - name: blue-api
        image: asia.gcr.io/hoge/color-api:blue
        ports:
        - containerPort: 80
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: green-api
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: color-api
        version: green
    spec:
      containers:
      - name: green-api
        image: asia.gcr.io/hoge/color-api:green
        ports:
        - containerPort: 80

ServiceでB/Gしてみる

KubernetesはServiceでアクセスするPodを見つけているので、そこの設定を書き換えればB/Gできそうです。

apiVersion: v1
kind: Service
metadata:
  name: service-color-api
spec:
  selector:
    app: color-api
    version: green
#     version: blue
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    name: http

versionをblueとgreenで変えてみた結果、このようになりました。

f:id:akaimo3:20180522222711p:plain

ちょっとわかりづらいですね。
点線の時間で切り替えを行い、Blue(上)からGreen(下)にトラフィックが切り替わるようにServiceを書き換えました。
Serviceの設定を更新したらすべてのトラフィックがGreenに行ってほしいところですが、10分ほど両方のPodにアクセスが行ってしまっています。

これではAPIのバージョニングができていないと不整合がおきてしまい正しく動かない恐れがあります。

なにか設定が漏れているのかもしれません。
原因を知っていれば教えてほしいです...

余談ですが、切り替えた直後に前の色のPodを削除することで、瞬時に全てのトラフィックを新しい色に流すことができます。

瞬時に100%のトラフィックを切り替えたいので別のアプローチとしてサービスメッシュのIstioを導入してみます。

IstioでB/Gする

まずはIstioの導入をします。
公式ドキュメントのステップ2まででIstioの導入は完了です。

Istioで経路の制御をするので、ServiceはBlueにもGreenにも通信ができるようにしておきます。

apiVersion: v1
kind: Service
metadata:
  name: service-color-api
  labels:
    app: color-api
spec:
  selector:
    app: color-api
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    name: http

Istioを使用する場合はIstio用のIngressを通さないと経路制御などができないので、少し設定に変更を入れます。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: color-ingress
  annotations:
    kubernetes.io/ingress.class: "istio"
spec:
  backend:
    serviceName: service-color-api
    servicePort: 80

最後に本命の経路制御の設定をいれます。

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: color-api
spec:
  destination:
    name: service-color-api
  precedence: 1
  route:
  - labels:
      version: green

今回もselectorを書き換えることで制御できます。
定期的なアクセスがある状態で切り替えるとこのようになりました。

f:id:akaimo3:20180523104428p:plain

取得できるメトリクスがCPU利用率しかなかったので、あまりいいグラフにはなっていませんが、切り替えた直後からすべてのトラフィックが新しいほうに流れています。
Prometheusを導入後に今度はアクセス数をグラフ化して追記したいとおもいます。

まとめ

サービスメッシュを入れるとトラフィックの正確な制御ができます。
しかし、制御のためのプロキシも追加されてしまうため、余計なリソースを使うことにもなってしまいます。

デメリットも存在しますが、Istioを入れてしまえばB/Gだけでなくカナリアリリースもできるようになるので、
リリースを正確に行うためにIstioを導入するのもありかもしれません。

Kubernetesに負荷をかけオートスケールを観察する

本番環境でKubernetesを使うためには、Kubernetesがどのように動作するか把握していないと安心して運用することができません。
まずはサービスを運用する上で重要になる、高負荷時の動きを確認してみたいと思います。

KubernetesにはHPAとCluster Autoscalerの二種類がありますが、両方の動きを見ていきます。

詳細な解説は他に譲りますが、
HPAは稼働中のPodを増加させることで高負荷に耐え、
Cluster AutoscalerはPodを増加させることができないときにKubernetesが扱えるリソースを追加するものです。

つまり、負荷が高まるとHPAがおこりPodが追加され、それでも負荷が高くHPAが続きKubernetesが管理しているリソースに空きがなくなったときにCluster AutoscalerによってVMが追加され、そこにPodが追加されます。

環境

  • GKE 1.9.7-gke.0

実行するアプリケーション

できるだけ手軽に実験したいので、goでリクエストを受け付けて、少し負荷がかかる処理をした後にレスポンスを返すアプリケーションをKubernetesで実行してみます。

Kubernetesの構成は
Ingress -> Service -> Deployment
となっています。

実行するgoのAPIのソースはこのようにしました。

package main

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

func handler(w http.ResponseWriter, r *http.Request) {
    n := 0
    for i := 0; i < 1*1000*1000*1000; i++ {
        n += i
    }
    fmt.Fprintf(w, strconv.Itoa(n)+"\n")
    fmt.Fprintf(w, "my-api")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":80", nil)
}

とても単純なものです。
これをDocker Imageにし、DockerのrunコマンドでAPIサーバーが起動するようにDockerfileを書きます。

FROM golang:1.9.2-alpine3.6

ADD ./server.go ./
RUN go build -o server
CMD ["/go/server"]

Imageをアップロードしますが、せっかくGKEを使用するので同じGCP上にあるGoogle Container Registryにアップロードします。

$ docker build -t asia.gcr.io/{your-project-id}/my-api:v1 .
$ gcloud docker -- push asia.gcr.io/{your-project-id}/my-api:v1

Kubernetesの設定ファイル

Kubernetesについては省略します。
今回は以下の設定ファイルを使用します。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: "test-ip"
spec:
  backend:
    serviceName: service-my-api
    servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: service-my-api
spec:
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    name: http
  selector:
    app: my-api
    version: v1
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: my-api
        version: v1
    spec:
      containers:
      - name: myapi
        image: gcr.io/{your-project-id}/myapi:v1
        resources:
          requests:
            cpu: "500m"
          limits:
            cpu: "1500m"
        ports:
        - containerPort: 80
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: my-api
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-api
  minReplicas: 2
  maxReplicas: 6
  targetCPUUtilizationPercentage: 70

オートスケールに関係のあるところだけ解説します。
今回負荷をかけるAPIがあるPodに対してはCPUを1.5個まで使用を許可しています。
このPodが初期値として2台稼働しており、閾値を70としています。

つまり、CPUの使用率が70%を超えたときにPodの追加が行われることになります。
また、公式ドキュメントのautoscaling-algorithmによると、10%の許容値があったり瞬間的に超えただけだとオートスケールしなかったりするようです。

負荷をかける

負荷をかけるツールはいろいろありますが、手軽に使えるheyを使用します。

github.com

このツールで負荷をかけてみた結果、このようになりました。

PodのCPU使用率 f:id:akaimo3:20180515231231p:plain

ノードのCPU使用率 f:id:akaimo3:20180515231244p:plain

ピーク時はPodは6個に、ノードは3台にまで増えていますね。
HPAは追加にかかる時間が数秒なので、EC2とかのオートスケールと比べるとだいぶ早いですね。
これは便利そうです。

Webpackでビルドするときにflowtypeで型をチェックする

flowを導入して型のチェックがされるようになっても、開発者がエディタで設定していなかったり、 エディタによっては開いていないソースはチェックの対象になっていなかったりします。

これではせっかく導入したflowが活かせません。

そこでWebpackでビルドと同時にflowの型チェックを走らせてみます。

プラグインの導入

flow-status-webpack-pluginを使用してビルド時にチェックします。

インストールします。

yarn add flow-status-webpack-plugin --dev

あとはwebpack.config.jsに設定を追加すれば完了です。
このファイルは環境によっては異なる場合があるので適宜読み替えてください。

var FlowStatusWebpackPlugin = require('flow-status-webpack-plugin');

module.exports = {
  ...,
  plugins: [
    ...,
    new FlowStatusWebpackPlugin({
      onSuccess: function (stdout) {
        console.log('\u001b[32m' + 'Succeeded in type check by flow' + '\u001b[0m');
      },
      onError: function (stdout) {
        console.log('\u001b[;41m' + ' Failed in type check by flow ' + '\u001b[0m');
        console.error(stdout);
      }
    })
  ]
}

補足

\u001b[32mなどはコンソールの出力に色をつけています。
成功したときは緑、失敗したときは赤になっています。

開発者に認識してもらうことが目的なので、このようなちょっとしたことも大切ですね。

QNAPにTwonky Serverをインストールする

QNAPが公式に提供しているDLNAサーバーはいろいろと貧弱なので、評判の高いTwonky Serverを使えるようにします。

環境

  • QNAP: TS-431P
  • OS: QTS 4.3.4

Twonky Serverのダウンロード

まずは以下のリンクからダウンロードページに移動します。

http://download.twonky.com/

次にインストールするバージョンを決めます。
今回は最新バージョンである8.5を選びました。

次に自分の環境にあったビルドを選びます。
ここで正しいものを選ばないと動かないので気をつけてください。
QNAP用には次のビルドが用意されています。

Qnap arm-x09 package
Qnap arm-x19 package
Qnap arm-x31 package
Qnap arm-x41 package
Qnap x86 package
Qnap x86-64 package

自分の環境を調べます。
別のソフトウェアですが、次のサイトが参考になります。

Kazoo Server ソフト QNAP NAS対応モデル | OLIOSPEC BLOG

私のQNAPはTS-431PですのでQnap arm-x41をダウンロードします。

Twonky Serverのインストール

QNAPにログインしてAppCenterを起動します。
AppCenterの右上にあるアイコンをクリックし、手動インストールの画面で先程ダウンロードしたqpkgを選択し、ダイアログに従っていけばインストールが完了します。