{"id":1346,"date":"2022-05-28T17:44:28","date_gmt":"2022-05-28T17:44:28","guid":{"rendered":"https:\/\/brakkee.org\/site\/?p=1346"},"modified":"2022-08-14T13:36:01","modified_gmt":"2022-08-14T13:36:01","slug":"hosting-services-on-google-kubernetes-engine","status":"publish","type":"post","link":"https:\/\/brakkee.org\/site\/2022\/05\/28\/hosting-services-on-google-kubernetes-engine\/","title":{"rendered":"Hosting services on Google Kubernetes Engine"},"content":{"rendered":"<p>This post explains how to host services on Google Compute Engine, parts of this are applicable to regular (non-GKE) Kubernetes clusters as well. This post will cover:<\/p>\n<ul>\n<li>allowing multiple services to be deployed in different namespaces<\/li>\n<li>allowing multiple (sub)domains to be deployed<\/li>\n<li>making a service available through a public and fixed IP accessible using a host name<\/li>\n<li>making it accessible over HTTPS using a certificate<\/li>\n<li>HTTP redirect to HTTPS<\/li>\n<\/ul>\n<p>The treatment of the subject is a bit high level with focus on what resources and fields are relevant and on the way different Kubernetes resources relate to each other. Also some snippets of YAML configuration are given. This should be sufficient to &#8216;roll your own&#8217;.<\/p>\n<p><!--more--><\/p>\n<h2>Deploying services in different namespaces<\/h2>\n<p>The basic architecture we start off with is as follows:<\/p>\n<p><img src=http:\/\/www.plantuml.com\/plantuml\/img\/uyh8J4bLICuiIiv9vKhEIImkLl3CIozA1TBLN7c9kQd51Od9gLorKBdWrAAopEHKh78kgGKv-PMvgNabQD8m0tLrxHIKj9HWXd3130EB6oeX6S050000 alt=\"PlantUML Syntax:<br \/>\nhide circle<br \/>\nclass Ingress {<br \/>\nnamespace<br \/>\n}<br \/>\nclass Service {<br \/>\nnamespace<br \/>\n}<br \/>\nclass Deployment {<br \/>\nnamespace<br \/>\n}<\/p>\n<p>Ingress &#8211;&gt; &#8220;*&#8221; Service<br \/>\nService &#8211;&gt; &#8220;1&#8221; Deployment<\/p>\n<p>\" usemap=\"#plantuml_map\"><\/p>\n<p>In this basic setup,\u00a0 there is one Ingress object in a certain namespace and each Service that it exposes is in a separate namespace together with its deployment. There is a limitation in Ingress that does not allow it to be used when Services are in a different namespace than the Ingress resource. This limitation is expected to be lifted in a future version of Kubernetes using the <a href=\"https:\/\/gateway-api.sigs.k8s.io\/\">Gateway API<\/a>.<\/p>\n<p>One workaround is to deploy a separate Ingress in every Service namespace. However, this leads to other problems since than separate IPs must be used for each service.<br \/>\nA better solution is to use a separate ingress namespace in which to deploy a reverse proxy (in the example httpd is used, but nginx is of course also possible) and from there to setup a reverse proxy using the cluster local DNS name for the service. Given that a service <em>x<\/em> is deployed in namespace <em>y<\/em>, it can be accessed using the DNS name <em>x.y.svc.cluster.local<\/em>.<\/p>\n<p>On apache, this can be a configuration such as this:<br \/>\n<code><br \/>\nProxyPass \/hello http:\/\/X.Y.svc.cluster.local\/hello<br \/>\nProxyPassReverse \/hello http:\/\/X.Y.svc.cluster.local\/jenkins<br \/>\n<\/code><\/p>\n<p>The architecture for two services resp. x in namespace <em>y<\/em> and <em>x2<\/em> in namespace <em>y2<\/em> then becomes:<\/p>\n<p><img src=http:\/\/www.plantuml.com\/plantuml\/img\/TP0z4y8W38Rt_WgElGoC7LnqSN1oTK4QQxK5hj1UUfx_NOz-qe-9aBnvyYQH4V8jCY1KYHBi_mS90JJQrXLGyedq_GNI4Ufpa9nLLe6rb0ZRoM1KF9qpQNA5giabU87Fs2wna9t05BejGRb9cVfSA8obLeyyIux-kGWpoRjAz8ssLcZdtZ5mXKj7k7I3lPDjDFA0pKCuNz7v2iz7VdT24iU7y5Zp3ncAVTSi9lIcaBNCDfB9ehOEAbPeAGhgaHZW-fQ1rfyFPrDmPm9dBT-TCIuyHXurxFW3 alt=\"PlantUML Syntax:<br \/>\npackage &#8220;namespace: exposure&#8221; {<br \/>\nobject &#8220;expose:Ingress&#8221; as expose<br \/>\nobject &#8220;httpd:Service&#8221; as httpdservice<br \/>\nobject &#8220;httpd:Deployment&#8221; as httpddeploy<br \/>\nobject &#8220;httpd-config:ConfigMap&#8221; as httpdconfig<br \/>\n}<br \/>\npackage &#8220;namespace: y&#8221; {<br \/>\nobject &#8220;x:Service&#8221; as xservice<br \/>\nobject &#8220;x:Deployment&#8221; as xdeploy<br \/>\n}<br \/>\npackage &#8220;namespace: x2&#8221; {<br \/>\nobject &#8220;x2:Service&#8221; as x2service<br \/>\nobject &#8220;x2:Deployment&#8221; as x2deploy<br \/>\n}<br \/>\nexpose &#8211;&gt; httpdservice<br \/>\nhttpdservice -&gt; httpddeploy<br \/>\nhttpddeploy &#8220;\\nx.y.svc.cluster.local&#8221; &#8211;&gt; xservice<br \/>\nhttpddeploy -&gt; httpdconfig<br \/>\nxservice -&gt; xdeploy<br \/>\nhttpddeploy &#8220;\\nx2.y2.svc.cluster.local&#8221; &#8211;&gt; x2service<br \/>\nx2service -&gt; x2deploy<\/p>\n<p>\" usemap=\"#plantuml_map\"><\/p>\n<p>This architecture is quite flexible. It allows SSL termination on a using a single Ingress rule and provides complete flexibility in allocating services to namespaces.<\/p>\n<p>The apache deployment is configured using a config map that contains the httpd.conf file in a <em>ConfigMap <\/em>resource.\u00a0The <em>ConfigMap<\/em> is mounted into the pod as follows:<\/p>\n<pre>apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: httpd\n  namespace: exposure\n  ...\nspec:\n  ...\n  template:\n    ...\n    spec:\n      containers:\n        - name: httpd\n          image: httpd:2.4\n          ...\n          volumeMounts:\n            - name: httpd-conf\n              mountPath: \/usr\/local\/apache2\/conf\/httpd.conf\n              subPath: httpd.conf\n      volumes:\n        - name: httpd-conf\n          configMap: \n            name: httpd-config\n<\/pre>\n<p>Note the use of the <em>subPath<\/em>\u00a0configuration in the deployment spec to mount only a single file from the httpd-conf volume, which keeps all other files in the <em>\/usr\/local\/apache2\/conf\/<\/em> directory intact. The config map can be created from an existing httpd.conf file as follows:<\/p>\n<pre>kubectl create configmap --namespace exposure \\\n  httpd-config --from-file=httpd.conf\n<\/pre>\n<p>The <em>httpd.conf<\/em> used is an adaptation of the file that comes standard with the container. Since we are terminating SSL using <em>Ingress<\/em>, the httpd config is without any SSL configuration or reference to certificate files.<\/p>\n<h2>Exposing the service externally<\/h2>\n<p>To expose the httpd service externally (and thereby all services for which it is a reverse proxy), we need to introduce the <em><a href=\"https:\/\/cloud.google.com\/kubernetes-engine\/docs\/how-to\/ingress-features\">FrontendConfig<\/a><\/em> resource. The <em>FrontendConfig<\/em> is a custom resource that is only available on GKE as far as I know and this is used to configure Ingress features.The SSL Policy is a GCP object that defines SSL features for the HTTPS connection.<\/p>\n<p><img src=http:\/\/www.plantuml.com\/plantuml\/img\/PK_B2iCW4Bpx5PAxF-2XK08jeJq4yWL6DyOcKL5BMqh_lQ9vqTvsTcPsPf5bV60IS76vj-0q1F05VYFJte07NC3J6W-qqjA1ZmppEAzMHUUC3g05DxfJafvcM2QO38i2VT2QTYxhVOmSWAYSGRmVYJMZuY_QDDSwJUbENkwgiScEqMTfPOz2h0MLPHdzIRcPQEsWwPa3KO8BgbES1KWslk7OD7-9YLEo3-ImFR4Cc0Zpq96PQos6URkL-27sCLy0 alt=\"PlantUML Syntax:<br \/>\npackage &#8220;Kubernetes&#8221; {<br \/>\nobject &#8220;expose:Ingress&#8221; as expose<br \/>\nobject &#8220;frontendconfig:FrontendConfig&#8221; as frontend<br \/>\n}<br \/>\npackage &#8220;GCP&#8221; {<br \/>\nobject &#8220;gke-ingress-ssl-policy:SSLPolicy&#8221; as policy<br \/>\nobject &#8220;example-ip:IpAddress&#8221; as ipaddress<br \/>\nobject &#8220;example-com:PreSharedCertificate&#8221; as certificate<br \/>\n}<\/p>\n<p>expose -right-&gt; frontend<br \/>\nfrontend -down-&gt; policy<br \/>\nexpose -down-&gt; ipaddress<br \/>\nexpose -down-&gt; certificate<br \/>\n\" usemap=\"#plantuml_map\"><\/p>\n<p>Using this setup, we can configure:<\/p>\n<ul>\n<li>the certificate used<\/li>\n<li>HTTP to HTTPS redirect<\/li>\n<li>the external IP address<\/li>\n<\/ul>\n<p>It is most easy to look at the Ingress configuration:<\/p>\n<pre>apiVersion: networking.k8s.io\/v1\nkind: Ingress\nmetadata:\n  name: httpd-ingress\n  annotations:\n    #kubernetes.io\/ingress.allow-http: \"false\"\n    kubernetes.io\/ingress.global-static-ip-name: example-ip\n    ingress.gcp.kubernetes.io\/pre-shared-cert: example-com\n    networking.gke.io\/v1beta1.FrontendConfig: frontendconfig\n  namespace: exposure\nspec:\n  # tls config not needed since a pre-shared certificate\n  # is used. \n  #tls:\n  #  - hosts:\n  #      - example.com\n  #    secretName: example-com-certificates\n\n  rules:\n    - host: example.com\n      http:\n        paths:\n          - path: \/\n            pathType: Prefix\n            backend:\n              service:\n                name: httpd\n                port:\n                  number: 80\n\n<\/pre>\n<p>First of all, we see that all traffic is forwarded to httpd, which is ok since httpd distributes the traffic over backend services and as far as Ingress just routes to httpd. Next, we see several other features.<\/p>\n<p>The annotation <em>kubernetes.io\/ingress.allow-http<\/em> can be used to disable http but this is commented out since we allow http and will do a redirect to https instead using the <em>FrontendConfig<\/em>.<\/p>\n<p>Next, the annotation <em>kubernetes.io\/ingress.global-static-ip-name<\/em> defines the name of a previously created static IP address in GCP which is the static IP address under which the services are exposed.<\/p>\n<p>The annotation <em>ingress.gcp.kubernetes.io\/pre-shared-cert<\/em> defines the name of the pre-shared certificate that is used.<\/p>\n<p>A pre-shared certificate is a certificate registered at GCP. Based on the domain name, GCP automatically chooses the certificate. This means that explicit configuration of the certificate using the <em>tls<\/em> section is not needed and this part is therefore commented out. It also allows the same certificate to be used by multiple GKE clusters and is thus lower maintenance than using the alternative setup by defining a secret per GKE cluster for the certificates.<\/p>\n<p>To create the pre-shared certificate, concatenate the crt files of the certificate containing the certificate and certificate chain and put it into a single file as follows:<\/p>\n<pre>  cat example.com.crt example.com.2022.crt &gt; full.crt\n<\/pre>\n<p>Next, create the pre-shared certificate from this file and the private key file:<\/p>\n<pre>gcloud compute ssl-certificates create example-com \\\n  --project example-project \\\n  --global \\\n  --certificate=full.crt \\\n  --private-key=example_2022.key\n<\/pre>\n<p>The final part is the <em>networking.gke.io\/v1beta1.FrontendConfig<\/em> annotation which links the ingress resource to the frontend config.<\/p>\n<p>The <em>FrontendConfig<\/em>, finally, is as follows:<\/p>\n<pre>apiVersion: networking.gke.io\/v1beta1\nkind: FrontendConfig\nmetadata:\n  name: frontendconfig\n  namespace: exposure\nspec:\n  redirectToHttps:\n    enabled: true\n  sslPolicy: gke-ingress-ssl-policy\n<\/pre>\n<p>In this configuration we see that the redirect from http to https is configured. Also, there is reference to an <a href=\"https:\/\/cloud.google.com\/load-balancing\/docs\/use-ssl-policies#gcloud\">SSL policy<\/a> which defines SSL features. For instance:<\/p>\n<pre>gcloud compute ssl-policies create gke-ingress-ssl-policy \\\n    --profile MODERN \\\n    --min-tls-version 1.2\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This post explains how to host services on Google Compute Engine, parts of this are applicable to regular (non-GKE) Kubernetes clusters as well. This post will cover: allowing multiple services to be deployed in different namespaces allowing multiple (sub)domains to &hellip; <a href=\"https:\/\/brakkee.org\/site\/2022\/05\/28\/hosting-services-on-google-kubernetes-engine\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[10],"tags":[],"_links":{"self":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts\/1346"}],"collection":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/comments?post=1346"}],"version-history":[{"count":78,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts\/1346\/revisions"}],"predecessor-version":[{"id":1723,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/posts\/1346\/revisions\/1723"}],"wp:attachment":[{"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/media?parent=1346"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/categories?post=1346"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/brakkee.org\/site\/wp-json\/wp\/v2\/tags?post=1346"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}