From 6162cac0fa2f32580b53648f41bc884e7608ed3a Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 27 Jun 2017 22:23:54 +0300 Subject: [PATCH] Add in-Kubernetes usage docs --- docs/content/advanced/kubernetes.md | 377 ++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 docs/content/advanced/kubernetes.md diff --git a/docs/content/advanced/kubernetes.md b/docs/content/advanced/kubernetes.md new file mode 100644 index 00000000..56276a58 --- /dev/null +++ b/docs/content/advanced/kubernetes.md @@ -0,0 +1,377 @@ +## Deployment example + +There is nothing much in deploying mailserver to Kubernetes itself. The things are pretty same as in [`docker-compose.yml`][1], but with Kubernetes syntax. + +```yaml +kind: Service +apiVersion: v1 +metadata: + name: mailserver + labels: + app: mailserver +spec: + selector: + app: mailserver + ports: + - name: smtp + port: 25 + targetPort: smtp + - name: smtp-auth + port: 587 + targetPort: smtp-auth + - name: imap-secure + port: 993 + targetPort: imap-secure + +--- + +kind: ConfigMap +apiVersion: v1 +metadata: + name: mailserver.config + labels: + app: mailserver +data: + postfix-accounts.cf: | + user1@example.com|{SHA512-CRYPT}$6$2YpW1nYtPBs2yLYS$z.5PGH1OEzsHHNhl3gJrc3D.YMZkvKw/vp.r5WIiwya6z7P/CQ9GDEJDr2G2V0cAfjDFeAQPUoopsuWPXLk3u1 + + postfix-virtual.cf: | + alias1@example.com user1@dexample.com + + SigningTable: | + *@example.com mail._domainkey.example.com + + KeyTable: | + mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com-mail.key + + TrustedHosts: | + 127.0.0.1 + localhost + +--- + +kind: Secret +apiVersion: v1 +metadata: + name: mailserver.opendkim.keys + labels: + app: mailserver +type: Opaque +data: + example.com-mail.key: 'base64-encoded-DKIM-key' + +--- + +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: mailserver + labels: + app: mailserver +spec: + strategy: + type: Recreate + template: + metadata: + labels: + app: mailserver + spec: + nodeSelector: + has/mail-server: 'true' + subdomain: mailserver + containers: + - name: mailserver + image: tvial/docker-mailserver:2.1 + ports: + - name: smtp + containerPort: 25 + - name: smtp-auth + containerPort: 587 + - name: imap-secure + containerPort: 993 + env: + - name: ONE_DIR + value: '1' + volumeMounts: + - name: config + subPath: postfix-accounts.cf + mountPath: /tmp/docker-mailserver/postfix-accounts.cf + readOnly: true + - name: config + subPath: postfix-virtual.cf + mountPath: /tmp/docker-mailserver/postfix-virtual.cf + readOnly: true + - name: config + subPath: SigningTable + mountPath: /tmp/docker-mailserver/opendkim/SigningTable + readOnly: true + - name: config + subPath: KeyTable + mountPath: /tmp/docker-mailserver/opendkim/KeyTable + readOnly: true + - name: config + subPath: TrustedHosts + mountPath: /tmp/docker-mailserver/opendkim/TrustedHosts + readOnly: true + - name: opendkim-keys + mountPath: /tmp/docker-mailserver/opendkim/keys + readOnly: true + - name: data + mountPath: /var/mail + - name: state + mountPath: /var/mail-state + volumes: + - name: config + configMap: + name: mailserver.config + - name: opendkim-keys + secret: + secretName: mailserver.opendkim.keys + - name: data + hostPath: + path: /path/to/mailserver/data + - name: state + hostPath: + path: /path/to/mailserver/state +``` + +__Note:__ +Any sensitive data (keys, etc) should be deployed via [Secrets][50]. Other configuration just fits well into [ConfigMaps][51]. + +__Note:__ +Make sure that [Pod][52] is [assigned][59] to specific [Node][53] in case you're using volume for data directly with `hostPath`. Otherwise Pod can be rescheduled on a different Node and previous data won't be found. Except the case when you're using some shared filesystem on your Nodes. + + + + +## Exposing to outside world + +The hard part with Kubernetes is to expose deployed mailserver to outside world. Kubernetes provides multiple ways for doing that. Each has its downsides and complexity. + +The major problem with exposing mailserver to outside world in Kubernetes is to [preserve real client IP][57]. Real client IP is required by mailserver for performing IP-based SPF checks and spam checks. + +Preserving real client IP is relatively [non-trivial in Kubernetes][57] and most exposing ways do not provide it. So, it's up to you to decide which exposing way suits better your needs in a price of complexity. + +If you do not require SPF checks for incoming mails you may disable them in [Postfix configuration][2] by dropping following line (which removes `check_policy_service unix:private/policyd-spf` option): +```yaml +kind: ConfigMap +apiVersion: v1 +metadata: + name: mailserver.config + labels: + app: mailserver +data: + postfix-main.cf: | + smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_unauth_pipelining, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_recipient_domain, reject_rbl_client zen.spamhaus.org, reject_rbl_client bl.spamcop.net +# ... + +--- + +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: mailserver +# ... + volumeMounts: + - name: config + subPath: postfix-main.cf + mountPath: /tmp/docker-mailserver/postfix-main.cf + readOnly: true +# ... +``` + + +### External IPs Service + +The simplest way is to expose mailserver as a [Service][55] with [external IPs][56]. + +```yaml +kind: Service +apiVersion: v1 +metadata: + name: mailserver + labels: + app: mailserver +spec: + selector: + app: mailserver + ports: + - name: smtp + port: 25 + targetPort: smtp +# ... + externalIPs: + - 80.11.12.10 +``` + +##### Downsides + +- __Real client IP is not preserved__, so SPF check of incoming mail will fail. + +- Requirement to specify exposed IPs explicitly. + + +### Proxy port to Service + +The [Proxy Pod][58] helps to avoid necessity of specifying external IPs explicitly. This comes in price of complexity: you must deploy Proxy Pod on each [Node][53] you want to expose mailserver on. + +##### Downsides + +- __Real client IP is not preserved__, so SPF check of incoming mail will fail. + + +### Bind to concrete Node and use host network + +The simplest way to preserve real client IP is to use `hostPort` and `hostNetwork: true` in the mailserver [Pod][52]. This comes in price of availability: you can talk to mailserver from outside world only via IPs of [Node][53] where mailserver is deployed. + +```yaml +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: mailserver +# ... + spec: + hostNetwork: true +# ... + containers: +# ... + ports: + - name: smtp + containerPort: 25 + hostPort: 25 + - name: smtp-auth + containerPort: 587 + hostPort: 587 + - name: imap-secure + containerPort: 993 + hostPort: 993 +# ... +``` + +##### Downsides + +- Not possible to access mailserver via other cluster Nodes, only via the one mailserver deployed at. + + +### Proxy port to Service via PROXY protocol + +This way is ideologically the same as [using Proxy Pod](#proxy-port-to-service) but instead Proxy Pod you should use [HAProxy image][11] or [Nginx Ingress Controller][12] and proxy TCP traffic to mailserver Pod with PROXY protocol usage which does real client IP preservation. + +This requires some additional mailserver configuration: you should enable PROXY protocol on ports that [Postfix][2] and [Dovecot][3] listen on for incoming connections. +```yaml +kind: ConfigMap +apiVersion: v1 +metadata: + name: mailserver.config + labels: + app: mailserver +data: + postfix-main.cf: | + smtpd_upstream_proxy_protocol = haproxy + dovecot.cf: | + service imap-login { + inet_listener imaps { + haproxy = yes + } + } +# ... + +--- + +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: mailserver +#... + volumeMounts: + - name: config + subPath: postfix-main.cf + mountPath: /tmp/docker-mailserver/postfix-main.cf + readOnly: true + - name: config + subPath: dovecot.cf + mountPath: /etc/dovecot/conf.d/zz-custom.cf + readOnly: true +# ... +``` + +##### Downsides + +- Not possible to access mailserver via inner cluster Kubernetes DNS, as PROXY protocol is required for incoming connections. + + + + +## Let's Encrypt certificates + +[Kube-Lego][10] may be used for a role of Let's Encrypt client. It works with Kubernetes [Ingress Resources][54] and automatically issues/manages certificates/keys for exposed services via Ingresses. + +```yaml +kind: Ingress +apiVersion: extensions/v1beta1 +metadata: + name: mailserver + labels: + app: mailserver + annotations: + kubernetes.io/tls-acme: 'true' +spec: + rules: + - host: example.com + http: + paths: + - path: / + backend: + serviceName: default-backend + servicePort: 80 + tls: + - secretName: mailserver.tls + hosts: + - example.com +``` + +Now, you can use Let's Encrypt cert and key from `mailserver.tls` [Secret][50] +in your [Pod][52] spec. + +```yaml +# ... + env: + - name: SSL_TYPE + value: 'manual' + - name: SSL_CERT_PATH + value: '/etc/ssl/mailserver/tls.crt' + - name: SSL_KEY_PATH + value: '/etc/ssl/mailserver/tls.key' +# ... + volumeMounts: + - name: tls + mountPath: /etc/ssl/mailserver + readOnly: true +# ... + volumes: + - name: tls + secret: + secretName: mailserver.tls +# ... +``` + + + + + +[1]: https://github.com/tomav/docker-mailserver/blob/master/docker-compose.yml.dist +[2]: https://github.com/tomav/docker-mailserver/wiki/Overwrite-Default-Postfix-Configuration +[3]: https://github.com/tomav/docker-mailserver/wiki/Override-Default-Dovecot-Configuration +[10]: https://github.com/jetstack/kube-lego +[11]: https://hub.docker.com/_/haproxy +[12]: https://github.com/kubernetes/ingress/tree/master/controllers/nginx#exposing-tcp-services +[50]: https://kubernetes.io/docs/concepts/configuration/secret +[51]: https://kubernetes.io/docs/tasks/configure-pod-container/configmap +[52]: https://kubernetes.io/docs/concepts/workloads/pods/pod +[53]: https://kubernetes.io/docs/concepts/architecture/nodes +[54]: https://kubernetes.io/docs/concepts/services-networking/ingress +[55]: https://kubernetes.io/docs/concepts/services-networking/service +[56]: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips +[57]: https://kubernetes.io/docs/tutorials/services/source-ip +[58]: https://github.com/kubernetes/contrib/tree/master/for-demos/proxy-to-service +[59]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node