Elastic Cloud on Kubernetes (ECK)で Kubernetesクラスタ上にElasticsearch, KibanaとAPM Serverを立ち上げ、外部のGo製APIサーバーのリクエストをトレースする。 クラスタはGKEで作成し、ノードプールはn2-highmem-4(2vCPU, 13GB)の3台にした。
インストール
ElasticSearchやKibana, APM ServerのCRDやelastic-operatorなどをインストールする。
KubernetesのCustom Resource Definition(CRD)とCustom Controller - sambaiz-net
$ kubectl apply -f https://download.elastic.co/downloads/eck/0.9.0/all-in-one.yaml
customresourcedefinition.apiextensions.k8s.io/apmservers.apm.k8s.elastic.co created
customresourcedefinition.apiextensions.k8s.io/elasticsearches.elasticsearch.k8s.elastic.co created
customresourcedefinition.apiextensions.k8s.io/trustrelationships.elasticsearch.k8s.elastic.co created
customresourcedefinition.apiextensions.k8s.io/kibanas.kibana.k8s.elastic.co created
clusterrole.rbac.authorization.k8s.io/elastic-operator created
clusterrolebinding.rbac.authorization.k8s.io/elastic-operator created
namespace/elastic-system created
statefulset.apps/elastic-operator created
secret/webhook-server-secret created
serviceaccount/elastic-operator created
$ kubectl get pod -n elastic-system
NAME READY STATUS RESTARTS AGE
elastic-operator-0 1/1 Running 1 91s
$ kubectl -n elastic-system logs -f statefulset.apps/elastic-operator
...
{"level":"info","ts":1569230702.0881083,"logger":"kubebuilder.controller","msg":"Starting workers","controller":"elasticsearch-controller","worker count":1}
{"level":"info","ts":1569230702.1144261,"logger":"kubebuilder.webhook","msg":"starting the webhook server."}
Resources
Elasticsearch
$ cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1alpha1
kind: Elasticsearch
metadata:
name: myes
spec:
version: 7.2.0
nodes:
- nodeCount: 1
config:
node.master: true
node.data: true
node.ingest: true
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
EOF
$ kubectl get elasticsearch
NAME HEALTH NODES VERSION PHASE AGE
myes green 1 7.2.0 Operational 4m
Podが立ち上がるまで少し時間がかかる。ユーザーelasticのパスワードはsecretにある。
$ kubectl get secret | grep 'myes'
myes-es-7jzg7sqxqt-certs Opaque 3 4m42s
myes-es-7jzg7sqxqt-config Opaque 1 4m42s
myes-es-elastic-user Opaque 1 4m42s
myes-es-http-ca-internal Opaque 2 4m43s
myes-es-http-certs-internal Opaque 2 4m43s
myes-es-http-certs-public Opaque 1 4m43s
myes-es-internal-users Opaque 3 4m42s
myes-es-transport-ca-internal Opaque 2 4m42s
myes-es-transport-certs-public Opaque 1 4m42s
myes-es-xpack-file-realm Opaque 3 4m42s
$ PASSWORD=$(kubectl get secret myes-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 -D)
$ kubectl port-forward service/myes-es-http 9200 &
$ curl -u "elastic:$PASSWORD" -k "https://localhost:9200" | jq ".cluster_name"
"myes"
Kibana
port-forwardなしで外からアクセスできるようにserviceをLoadBalancerにした。
$ cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1alpha1
kind: Kibana
metadata:
name: mykibana
spec:
version: 7.2.0
nodeCount: 1
http:
service:
spec:
type: LoadBalancer
elasticsearchRef:
name: myes
EOF
$ kubectl get kibana
NAME HEALTH NODES VERSION AGE
mykibana green 1 7.2.0 1m
$ open https://$(kubectl get service mykibana-kb-http -o=jsonpath='{.status.loadBalancer.ingress[0].ip}:{.spec.ports[0].port}')
ユーザーelasticでログインできる。
ApmServer
今回はクラスタ外から送るので同じくLoadBalancerにする。
$ cat <<EOF | kubectl apply -f -
apiVersion: apm.k8s.elastic.co/v1alpha1
kind: ApmServer
metadata:
name: myapm-server
spec:
version: 7.2.0
nodeCount: 1
http:
service:
spec:
type: LoadBalancer
elasticsearchRef:
name: myes
EOF
$ kubectl get apmserver
NAME HEALTH NODES VERSION AGE
myapm-server green 1 7.2.0 9m
KibanaからAPM Serverが動いていることを確認する。
APM Agentの設定
APM Agentをモニタリングするアプリケーションに入れる。
$ go get go.elastic.co/apm
$ export ELASTIC_APM_SERVER_URL=https://$(kubectl get service myapm-server-apm-http -o=jsonpath='{.status.loadBalancer.ingress[0].ip}:{.spec.ports[0].port}')
$ export ELASTIC_APM_SECRET_TOKEN=$(kubectl get secret/myapm-server-apm-token -o=jsonpath='{.data.secret-token}' | base64 -D)
module/apmhttpや module/apmsqlを使うと、 リクエスト全体のTransactionに対してそれぞれの処理部分がSpanとしてトレースされる。 contextを渡してやらないとTransactionを追跡できずSpanが作成されない。 自前でTransactionやSpanを作成することもできる。
import (
"net/http"
"go.elastic.co/apm/module/apmhttp"
"go.elastic.co/apm/module/apmsql"
_ "go.elastic.co/apm/module/apmsql/mysql"
)
var tracingClient = apmhttp.WrapClient(http.DefaultClient)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
db, err := apmsql.Open("mysql", dsn)
tracingClient.Do(req.WithContext(ctx))
fmt.Fprintf(w, "ok")
})
http.ListenAndServe(":8080", apmhttp.Wrap(mux))
}
実行しCheck agent statusしてもNo data has been received from agents yet
から変わらない。
デフォルトでログが出ないのでELASTIC_APM_LOG_FILEに値を入れてログを見てみたところ証明書で引っかかっていることが分かった。
ELASTIC_APM_VERIFY_SERVER_CERTをfalseにしたところ送られるようになったのでLaunch APMする。
$ export ELASTIC_APM_LOG_FILE=stderr
{"level":"error","time":"2019-10-01T19:18:15+09:00","message":"config request failed: sending config request failed: Get https://104.155.169.150:8200/config/v1/agents?service.name=isucari: x509: cannot validate certificate for 104.155.169.150 because it doesn't contain any IP SANs"}
$ export ELASTIC_APM_VERIFY_SERVER_CERT=false
ISUCON9の予選問題に入れてみた。 リクエスト中の外部サーバーへのリクエストやDBのクエリのタイミングやかかっている時間がTimeline上に表示される。
参考
How to instrument your Go application with the Elastic APM Go agent | Elastic Blog