GitOpsがやりたくてArgoCDを導入しようと思ったんだけど,そもそもArgoCD自体はみんなどこで動かしてるんです?
もちろんin clusterでも動くので,デプロイ対象のアプリケーションが乗っているKubernetesクラスタに構築しようと思いました.
が,ArgoCDのチュートリアルを試すという意味では,みんなport-forwardを使う例が多くて(確かに圧倒的に楽なんだけど),本番で使うことを前提に構築している例があまり見当たらない. ので,AWS EKS上に,外部(といっても社内とか)からアクセスできるArgoCDを,helmを使って構築する例を書き残しておく.
とりあえずインストールしてみる
helm chartはここにある.
- name: argocd namespace: argo chart: argo/argo-cd version: 2.0.3
みたいなhelmfileをかけばインストールできた.ここまではすごく簡単.
公式のガイドに従ってCLIを入れておく.
そして
$ argocd login my-argocd.h3poteto.dev
としたいのだが,もちろんインストールしたArgoCDは,まだどこにもingressを作っていないし,Route53の設定等もしていないので, my-argocd.h3poteto.dev
の名前解決ができるわけがない.
証明書が必要になる
ArgoCDはhttpsとgRPCの通信を行う. このとき,当然ながらTLSで通信するように作られている.となると,TLSの終端を誰に任せるかという問題が出てくる.また,証明書を誰が発行し,どのように管理するかという問題も発生する.
ArgoCDは起動時のパラメータに --insecure
を付けない限り,ArgoCDのサーバ自身がTLSの終端になってくれる.ただし,ここで用意されている証明書には,当然のことながら my-argocd.h3poteto.dev
みたいなドメインは含まれていない.そのため単にArgoCDのサーバを起動し,そこに外からアクセスできるELBやALBを作成しても,証明書のドメイン不一致となり警告が表示される.
というわけで, my-argocd.h3poteto.dev
のドメインで証明書を発行しつつ,それをArgoCDに使ってもらう必要がある.
目指す構成
というわけで以下のような構成を目指した.
Nginx Ingress Controller
helm-chartでは,ingressのリソースが用意されている.
これを利用するためには,server.ingress.enabled
をtrueにし,その他必要な情報を入れる必要がある.
ただ,annotationsはvaluesで上書きできるようになっているので,Nginx Ingress Controllerを使うには十分なtemplateになっている.
これと,後述するCertManagerを使うことで,任意のドメインの証明書をLet's Encryptから取得し,NginxにTLSの終端を任せることができる.
CertManager
Nginx Ingress Controllerで利用する証明書を管理してくれる.これのおかげで,俺が手動でLet's Encryptから証明書を取得して設定したり,更新したりする必要がなくなる.
Network Load Balancer
Nginx Ingress Controllerを作るだけでは,クラスタの外部からアクセスすることができない.
そのため,Nginx Ingress Controllerに外からアクセスできる口を作るために,Network Load Balancerを用意した. これについては,公式でもガイドが用意されているので,詳しくはこちらを参照してほしい.
構築
Nginx Ingress Controller
まず,NLBで外部から疎通できるようにした,Nginx Ingress Controllerを作成する.
以下のガイドに従う.
このガイドでは,Ingress Controllerを
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
として入れている.
今回俺はhelmで構築しているので,ここを
- name: nginx-ingress namespace: ingress-nginx chart: stable/nginx-ingress version: 1.35.0
このようなhelmfileで代替する.
このときに一点注意することがある.ガイドでは,この後,
$ kubectl apply -f https://raw.githubusercontent.com/cornellanthony/nlb-nginxIngress-eks/master/nlb-service.yaml
として,
kind: Service apiVersion: v1 metadata: name: ingress-nginx namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx annotations: # by default the type is elb (classic load balancer). service.beta.kubernetes.io/aws-load-balancer-type: nlb spec: # this setting is to make sure the source IP address is preserved. externalTrafficPolicy: Local type: LoadBalancer selector: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx ports: - name: http port: 80 targetPort: http - name: https port: 443 targetPort: https
このようなyamlをapplyしている.ここでNLBが作成されるわけだが,実はこのService,helm chart内にすでに定義されている.
そのため,helm installでnginx ingress controllerを入れた後に,このyamlをapplyすると,Serviceが二重に定義されることになる. というわけで, nlb-service.yamlのapplyは不要である. 代わりに,
- name: nginx-ingress namespace: ingress-nginx chart: stable/nginx-ingress version: 1.35.0 values: - rbac: create: true - controller: publishService: enabled: true service: enabled: true type: LoadBalancer externalTrafficPolicy: Local targetPorts: http: http https: https annotations: service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp" service.beta.kubernetes.io/aws-load-balancer-type: nlb
このようにして,helm chart内のServiceをNLBにしてしまう.
$ kubectl get pods -n ingress-nginx NAME READY STATUS RESTARTS AGE nginx-ingress-controller-cbf4bc7c9-7r9vm 1/1 Running 0 27h nginx-ingress-default-backend-7db6cc5bf-hw8rr 1/1 Running 0 27h $ kubectl get svc -n ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-ingress-controller LoadBalancer 172.20.65.116 <pending> 80:31385/TCP,443:30137/TCP 27h nginx-ingress-default-backend ClusterIP 172.20.252.236 <none> 80/TCP 27h
これでNginx Ingress Controllerの準備はできた.
Cert Manager
インストール
もちろんhelmで入れる.
- name: cert-manager namespace: kube-system chart: jetstack/cert-manager version: v0.14.1
これだけ.
Issuer
CertManagerで証明書を得るためには,手動でIssuerを立ててやる必要がある.
issuer.yaml
を作る.
apiVersion: cert-manager.io/v1alpha2 kind: ClusterIssuer metadata: name: letsencrypt-staging spec: acme: # The ACME server URL server: https://acme-staging-v02.api.letsencrypt.org/directory # ↑はstagingのURL.本番で使う場合は↓に書き換えること # server: https://acme-v02.api.letsencrypt.org/directory # Email address used for ACME registration email: hogehoge@example.com # Name of a secret used to store the ACME account private key privateKeySecretRef: name: my-argocd-tls solvers: - selector: dnsZones: - "my-argocd.h3poteto.dev" dns01: route53: region: ap-northeast-1 hostedZoneID: ASDF0234512312 # IRSAで認証させるので認証情報は書かない
$ kubectl apply -f issuer.yaml
solversにはdns01を使っている.ドメインの確認を,http-01でやるかdns-01でやるかは自由なのだが,dns-01の方が楽なので(更新とか)dns01にしている. DNS認証させるということは,当然Route53へのアクセス権が必要である. その場合,本来であればここにAWSのアクセスキー等を書くか,Roleの情報を書くのだが,
ここをIRSAで解決したいので,何も書かない. IRSAでの認証については次項で.
CertManagerからRoute53への認証
ここの認証はIAM Role for Service Accountに対応している.
そのため,Issuerの定義では認証情報を書かなかった.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "route53:GetChange", "Resource": "arn:aws:route53:::change/*" }, { "Effect": "Allow", "Action": [ "route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets" ], "Resource": "arn:aws:route53:::hostedzone/*" }, { "Effect": "Allow", "Action": "route53:ListHostedZonesByName", "Resource": "*" } ] }
resource "aws_iam_policy" "cert_manager_policy" { name = "cert-manager-policy" path = "/" description = "" policy = file("aws_iam_policies/cert_manager_policy.json") }
こんなPolicyを作って,IRSAできるIAM Roleに付与する.
resource "aws_iam_role" "cert_manager_role" { name = "cert-manager-role" path = "/" assume_role_policy = data.template_file.irsa_assume_role_policy.rendered }
resource "aws_iam_policy_attachment" "cert_manager_policy" { name = "cert-manager" roles = [ aws_iam_role.cert_manager_role.name, ] policy_arn = aws_iam_policy.cert_manager_policy.arn }
IRSAの詳しい設定については,こちらで書いているので参考にしてほしい.
そしたら,このIAM RoleをCertManagerのServiceAccountのAnnotationsに付与する.
- name: cert-manager namespace: kube-system chart: jetstack/cert-manager version: v0.14.1 values: - serviceAccount: create: true annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/cert-manager-role
これで,Issuerに定義したHostedZoneに,IRSAで付与されたIAM Roleの権限でアクセスできるようになる.
ArgoCD
Certificate
ArgoCDのhelm chartには,CertManager用のtemplateが用意されているので,こいつを有効化して,証明書を取得できるようにしてやる.
- name: argocd namespace: argo chart: argo/argo-cd version: 2.0.3 values: - server: certificate: domain: "my-argocd.h3poteto.dev" enabled: true issuer: kind: ClusterIssuer name: letsencrypt-staging
これでCertificateリソースができる.
$ kubectl get cert -n argo NAME READY SECRET AGE argocd-server True argocd-secret 5h22m
$ kubectl describe cert argocd-server -n argo Name: argocd-server Namespace: argo API Version: cert-manager.io/v1alpha2 Kind: Certificate ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal GeneratedKey 110s (x2 over 112s) cert-manager Generated a new private key Normal Requested 110s (x2 over 112s) cert-manager Created new CertificateRequest resource "argocd-server-1787752625" Normal PrivateKeyLost 110s cert-manager Lost private key for CertificateRequest "argocd-server-1787752625", deleting old resource Normal Issued 4s cert-manager Certificate issued successfully
Certificate issued successfully
になってれば大丈夫.
Ingress
上記で作成されたCertificateを使ってNginx Ingressを作る.
- name: argocd namespace: argo chart: argo/argo-cd version: 2.0.3 values: - server: certificate: domain: "my-argocd.h3poteto.dev" enabled: true issuer: kind: ClusterIssuer name: letsencrypt-staging extraArgs: # 証明書はCertManagerに取得させnginx-ingress-controllerで終端させるので,insecureで良い - --insecure ingress: enabled: true hosts: - my-argocd.h3poteto.dev annotations: cert-manager.io/cluster-issuer: letsencrypt-staging cert-manager.io/issuer-kind: ClusterIssuer kubernetes.io/ingress.class: nginx kubernetes.io/tls-acme: "true" nginx.ingress.kubernetes.io/ssl-passthrough: "true" tls: - hosts: - my-argocd.h3poteto.dev # secretNameはCertificate側で決め打ちされているので変更しないこと # https://github.com/argoproj/argo-helm/blob/master/charts/argo-cd/templates/argocd-server/certificate.yaml#L27 secretName: argocd-secret
これでingressが作成される.
$ kubectl describe ingress argocd-server -n arg Name: argocd-server Namespace: argo Address: Default backend: default-http-backend:80 (<none>) TLS: argocd-secret terminates my-argocd.h3poteto.dev Rules: Host Path Backends ---- ---- -------- my-argocd.h3poteto.dev / argocd-server:80 (10.5.29.125:8080) Annotations: cert-manager.io/cluster-issuer: letsencrypt-staging cert-manager.io/issuer-kind: ClusterIssuer kubernetes.io/ingress.class: nginx kubernetes.io/tls-acme: true nginx.ingress.kubernetes.io/ssl-passthrough: true Events: <none>
ちゃんと,my-argocd.h3poteto.dev
の証明書になっている.
これで構築は終わり.
ログインしてみる
$ argocd login my-argocd.h3poteto.dev --grpc-we Username: admin Password: 'admin' logged in successfully Context 'my-argocd.h3poteto.dev updated
いけたよ!!!
もちろん,WebUIも https://my-argocd.h3poteto.dev から確認できました.
ただしNLBを使っているのでSecurityGroupに注意する必要がある. NLB自体にSGの設定はできず,アクセス元のIPを維持したままリクエストを流すので,EKSのノード側のSGでアクセス元のIPを許可してやる必要がある.
社内からアクセスするのであれば,オフィスのIP等からのアクセスを許可する必要がある. そいういう制約上,PrivateSubnetに置かれたNodeでこれを運用することはできないんじゃないかなぁ.
もちろん,Nginx Ingressまで疎通すればいいので,IngressだけPublicSubnetに置いてあれば良さそうではあるが.ここは試してないのでわからない.
試行錯誤の履歴
ここからは,この構成に至るまでに試して,撃沈したことについて書いていく.
ServiceはClusterIP or NodePortにしてALB Ingress Controllerを使ってみる
ちなみにここまで読んできて,「ALB使ってACM設定すれば良くない?」と思った方.
ALBs and Classic ELBs don't fully support HTTP2/gRPC, which is used by the argocd CLI. Thus, when using an AWS load balancer, either Classic ELB in passthrough mode is needed, or NLBs.
なんですよ. ちなみに,実際にServiceをNodePortにして,ALB Ingress Controllerを使ってALBでIngressを構築してみたのだが,エラーが出て通信できなかったのであった. むしろこれをALBで疎通する方法があったら教えてほしい.
というわけで早々にALBについては諦めました.
ServiceをNLBにしてみる
最初,ArgoCDが用意しているServiceを,そのまま type: LoadBalancer
にして,NLBにしちゃえばいいじゃん!と思ったのだが,もちろんTLSの終端はArgoサーバになるわけで, my-argocd.h3poteto.dev
なんて許可されてないわけで.
当然証明書エラーが表示された.
また,type: LoadBalancer
でNLBを使う場合に,service.beta.kubernetes.io/aws-load-balancer-ssl-cert
が使えるものかと思っていたんだけど,今の手元のクラスタ(v1.14.9-eks)ではこれを指定してもNLBに証明書をが設定されることはなかった.
まぁここを手動でACM指定しても,やっぱり証明書のエラーが出ることには変わりなかったのだが.
まとめ
ひとまずArgoCDをEKS上で使える状態にすることはできた.
しかし本当にみんなArgoCDはどこに置いてデプロイしているんだろうか……. CDをやるコンポーネントである以上,あんまりデプロイ対象のクラスタの上に乗せたくはない気がするのだが…….