LNDのGKE化がうまくいかない

LND-BTCD環境をGKE上に構築し、lndのpeer(port:9375)の接続が可能な状態とすることを目的とする。
最終目的としては、BTCD-LND-LNCLIのnetworkはセキュアな状態で、lndのpeer(port:9375)だけをinternetに公開したい。

アーキテクチャ

前提

  1. GCPプロジェクト準備済
  2. GCLOUDをinstall済
  3. dockerをinstall済
  4. kubectlをinstall済

クラスタのセットアップ

まずはクラスタを作る。 対象のプロジェクトが存在するconfigurationを指定する。

gcloud config configurations activate CONFIGURATION_NAME  

続いて、空のクラスタを作成する。

gcloud container clusters create lnd-simnet --machine-type=n1-standard-1 --num-nodes=3 --region asia-northeast1-a  
gcloud container clusters describe lnd-simnet  

続いて、今後のローカルでのkubectlとの連携のために、credentialを渡す。

$ gcloud container clusters get-credentials lnd-simnet --zone asia-northeast1-a
Fetching cluster endpoint and auth data.  
kubeconfig entry generated for test-cluster.  

対象のコンテキストが指定されていることを確認する。

$ kubectl config get-contexts
~/G/s/LND_container ❯❯❯ kubectl config get-contexts                                                                                                                           2019/10/13 20:22:05 +[master]
CURRENT   NAME                                                 CLUSTER                                              AUTHINFO                                             NAMESPACE  
          docker-desktop                                       docker-desktop                                       docker-desktop                                       
          docker-for-desktop                                   docker-desktop                                       docker-desktop                                       
*         gke_asia-northeast1-a_lnd-simnet   gke_asia-northeast1-a_lnd-simnet   gke_asia-northeast1-a_lnd-simnet  

docker imageのpush

LND、BTCDともに、最新のgithubのソースを利用する。

  1. LND : https://github.com/lightningnetwork/lnd.git
  2. BTCD : https://github.com/btcsuite/btcd.git

上記のイメージを構築し、事前にGCR上に保存する必要がある。 まず、Container Registry を認証するには、gcloud を Docker 認証ヘルパーとして使用する。

gcloud auth configure-docker  

これができたら、imageをbuildし、gcrにpushする。 ホスト名と Google Cloud Platform Console のプロジェクト ID とイメージ名を組み合わせたものを指定する必要がある。

[HOSTNAME]/[PROJECT-ID]/[IMAGE]

docker build . -t  gcr.io/<PROJECT_NAME>/lnd:latest  
docker build . -t  gcr.io/<PROJECT_NAME>/btcd:latest  

下記を実行すると、下記の用にイメージが表示されるはずだ。

$ docker images
REPOSITORY                                                  TAG                 IMAGE ID            CREATED             SIZE  
gcr.io/<project>/btcd                               latest              aaaaaaaaaaaa        25 hours ago        59.7MB  
gcr.io/<project>/lnd                                latest              aaaaaaaaaaaa        25 hours ago        72.7MB  

続いて、このイメージをGCRにプッシュする。

docker push gcr.io/<PROJECT_NAME>/btcd:latest  
docker push gcr.io/<PROJECT_NAME>/lnd:latest  

GCPコンソールでGCRを確認すると、イメージが存在する。

コマンドでも良い。

gcloud container images list  

node・podの構築

上記のimageを使ってpodを構築する。

kubectl apply -f LND.yaml  
kubectl apply -f service.yaml  

上記コマンドにより、pod、serviceが構築される。

~/G/s/LND_container ❯❯❯ kubectl get pod                                                                                                                                       2019/10/13 20:03:17 +[master]
NAME         READY   STATUS    RESTARTS   AGE  
lnd-btcd-0   2/2     Running   1          3h53m  
~/G/s/LND_container ❯❯❯ kubectl get service                                                                                                                                   2019/10/13 20:03:41 +[master]
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                         AGE  
kubernetes   ClusterIP      10.43.240.1     <none>          443/TCP                         10d  
lnd-btcd-s   LoadBalancer   10.43.253.226   35.221.78.222   8080:32059/TCP,9735:30447/TCP   5h16m  

動作確認(エラーあり)

この状態で、LNDのpodに入り、LNDの情報を確認したい。

kubectl exec -it lnd-btcd-0 -c lnd bash  

これでコンテナにログインできる。

bash-5.0 lncli unlock  
Input wallet password: 

lnd successfully unlocked!  
bash-5.0 lncli --network=simnet getinfo  
[lncli] rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp 127.0.0.1:10009: connect: connection refused"

ここで、connectionがrefuseされる。
どこのコネクションが拒否されているかというと、LND serverに対するlncliのrpc接続だ。cli toolにrpcサーバをローカルで接続させるのはよくある構成である。イメージとしては、下記の構成が同一サーバ上に存在しているのと等しい。

エラー原因調査

エラーメッセージ"transport: Error while dialing dial tcp 127.0.0.1:10009: connect: connection refused"を素直に見れば、lncliからlndに接続しようした時に、エラーが発生していることになる。
lndとlncliは同じコンテナ上に存在するアプリケーションだが、tls証明書をつかった接続をしていおり、tls証明書がうまく作用していないように見える。

細かい挙動を今追っているが、serviceでロードバランサが構築される際に、IPアドレスが定まらないことが原因ではないか。

対応

tls証明書は、LNDのbootstrap時に実行されるが、このtls証明書は使えない。なので、LightningPeach/lnd-gc-deployを参考に、tls証明書の再作成をしてみる。

LightningPeachの考え方は、CFSSL (CloudFlare's PKI/TLS toolkit) を使って、 LNDがbootstrup時に実行する操作をなぞり、tls証明書を作り直すアプローチだ。
CFSSLは、自己署名認証局(CA)で証明書を作成するコンテナで、証明書作成用のコマンドを内包した、非常駐のDockerコンテナである。

利用するconfigは下記の通り。

{
    "signing": {
        "default": {
            "expiry": "87600h",
            "usages": [
                "signing",
                "key encipherment",
                "server auth",
                "client auth"
            ]
        }
    }
}

このコンフィグを、コンテナにコピーする。

kubectl cp config.json lnd-btcd-0:/config.json -c lnd  

続いて、tlsを作成するために必要なserver.jsonを作成する。

{
    "CN": "lnd",
    "hosts": [
        "address",
        "localhost",
        "0.0.0.0",
        "127.0.0.1"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [
        {
            "C": "EU"
        }
    ]
}

ここにあるaddressを、serviceのロードバランサが作成するexternalIPに置き換えることで作成し、GKEにコピーする。

IP=$(kubectl get services | grep lnd-pod | awk '{print $4}')  
sed -i ".bak" "s/address/$IP/g" server.json  
kubectl cp server.json lnd-pod:/server.new.json  

続いて、コンテナにログインして、証明書を作成し直す。

kubectl exec -it lnd-btcd-0 -c lnd bash  
cfssl gencert -initca server.new.json | cfssljson -bare ca -  
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=config.json server.new.json | cfssljson -bare server  
mv /server.pem /root/.lnd/tls.cert  
mv /server-key.pem /root/.lnd/tls.key  

これでしっかり動く!と思いきや、エラーがでる・・・

bash-5.0 lncli --network=simnet getinfo  
[lncli] rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: x509: certificate signed by unknown authority"

違うエラーではあるものの・・・。 LightningPeach/lnd-gc-deployを参考に、podを作り直してみる。

~/G/s/LND_container ❯❯❯ kubectl apply -f LND.yaml                                                                                                                             2019/10/13 15:09:24 +[master]
statefulset.apps/lnd-btcd created  
~/G/s/LND_container ❯❯❯ kubectl get pods                                                                                                                                      2019/10/13 15:10:06 +[master]
NAME         READY   STATUS    RESTARTS   AGE  
lnd-btcd-0   2/2     Running   0          36s  
~/G/s/LND_container ❯❯❯ kubectl exec -it lnd-btcd-0 -c lnd bash 
bash-5.0 lncli --network=simnet getinfo  
[lncli] rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp 127.0.0.1:10009: connect: connection refused"

同じエラー。なぜだ!