├── Dockerfile ├── README.md ├── kubernetes-config ├── ingress.yaml ├── kubernetes-cloudflare-sync.yaml ├── namespace.yaml ├── nginx-configmap.yaml └── php-app-deployment.yaml └── public ├── assets └── style.css └── index.php /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.2-fpm-alpine 2 | 3 | # Install application/service 4 | ENV APP_DIR /server/http/ 5 | WORKDIR ${APP_DIR} 6 | COPY . ${APP_DIR} 7 | 8 | RUN set -ex \ 9 | ## Customize PHP fpm configuration 10 | && sed -i -e "s/;clear_env\s*=\s*no/clear_env = no/g" /usr/local/etc/php-fpm.conf \ 11 | && sed -i -e "s/;request_terminate_timeout\s*=[^\n]*/request_terminate_timeout = 300/g" /usr/local/etc/php-fpm.conf \ 12 | && php-fpm --test 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes configuration 2 | 3 | This repository contains K8s configuration for nginx / php-fpm applications that aren't just APIs! A shared volume is used to mount static files (js, css, etc) on both nginx and fpm containers, so that you can use this setup for a full website. 4 | 5 | It also includes nginx as an ingress controller, to save costs on GKE (as their HTTP Load Balancer isn't so cheap). 6 | 7 | The setup looks as follows: 8 | 9 | ``` 10 | $ kubectl get deployment 11 | NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 12 | php-app 1 1 1 1 33m 13 | 14 | $ kubectl get daemonset 15 | NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE 16 | ingress 3 3 3 3 3 26m 17 | 18 | $ kubectl get configmap 19 | NAME DATA AGE 20 | ingress-conf 1 1h 21 | nginx-config 1 52m 22 | 23 | $ kubectl get service 24 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 25 | kubernetes ClusterIP 10.11.240.1 443/TCP 7h 26 | php-app ClusterIP 10.11.249.39 8080/TCP 35m 27 | 28 | $ kubectl get pod 29 | NAME READY STATUS RESTARTS AGE 30 | ingress-2b6sq 1/1 Running 0 26m 31 | ingress-878km 1/1 Running 0 26m 32 | ingress-t8gwq 1/1 Running 0 26m 33 | php-app-55648d549c-drqk5 2/2 Running 0 8m 34 | ``` 35 | 36 | ## Usage 37 | 38 | - Change [the name of the container](https://github.com/kieranajp/k8s-php-fpm/blob/master/kubernetes-config/php-app-deployment.yaml#L27), as this is referencing one of mine and it won't work for you! 39 | - Build the container and push it to gcloud - see [here][1] for info 40 | - `kubectl apply -f kubernetes-config/` 41 | 42 | ### Acknowledgements 43 | 44 | Thanks to [Caleb Doxsey's blog post][1] and [Matthew Palmer's blog post][2] for helping me piece this together. 45 | 46 | 47 | [1]: http://www.doxsey.net/blog/kubernetes--the-surprisingly-affordable-platform-for-personal-projects 48 | [2]: https://matthewpalmer.net/kubernetes-app-developer/articles/php-fpm-nginx-kubernetes.html 49 | -------------------------------------------------------------------------------- /kubernetes-config/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: ingress 5 | labels: 6 | app: ingress 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: ingress 11 | updateStrategy: 12 | type: RollingUpdate 13 | template: 14 | metadata: 15 | labels: 16 | app: ingress 17 | spec: 18 | hostNetwork: true 19 | dnsPolicy: ClusterFirstWithHostNet 20 | containers: 21 | - image: nginx:1.15.3-alpine 22 | name: nginx 23 | ports: 24 | - name: http 25 | containerPort: 80 26 | hostPort: 80 27 | volumeMounts: 28 | - name: "config" 29 | mountPath: "/etc/nginx" 30 | volumes: 31 | - name: config 32 | configMap: 33 | name: ingress-conf 34 | 35 | --- 36 | 37 | apiVersion: v1 38 | kind: ConfigMap 39 | metadata: 40 | name: ingress-conf 41 | data: 42 | nginx.conf: | 43 | worker_processes 1; 44 | error_log /dev/stdout info; 45 | 46 | events { 47 | worker_connections 10; 48 | } 49 | 50 | http { 51 | access_log /dev/stdout; 52 | 53 | server { 54 | listen 80; 55 | location / { 56 | proxy_pass http://php-app.default.svc.cluster.local:8080; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /kubernetes-config/kubernetes-cloudflare-sync.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: kubernetes-cloudflare-sync 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1beta1 7 | kind: ClusterRole 8 | metadata: 9 | name: kubernetes-cloudflare-sync 10 | rules: 11 | - apiGroups: [""] 12 | resources: ["nodes"] 13 | verbs: ["list", "watch"] 14 | --- 15 | apiVersion: rbac.authorization.k8s.io/v1beta1 16 | kind: ClusterRoleBinding 17 | metadata: 18 | name: kubernetes-cloudflare-sync-viewer 19 | roleRef: 20 | apiGroup: rbac.authorization.k8s.io 21 | kind: ClusterRole 22 | name: kubernetes-cloudflare-sync 23 | subjects: 24 | - kind: ServiceAccount 25 | name: kubernetes-cloudflare-sync 26 | namespace: default 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: kubernetes-cloudflare-sync 32 | labels: 33 | app: kubernetes-cloudflare-sync 34 | spec: 35 | replicas: 1 36 | selector: 37 | matchLabels: 38 | app: kubernetes-cloudflare-sync 39 | template: 40 | metadata: 41 | labels: 42 | app: kubernetes-cloudflare-sync 43 | annotations: 44 | config-version: "2" 45 | spec: 46 | serviceAccountName: kubernetes-cloudflare-sync 47 | terminationGracePeriodSeconds: 0 48 | containers: 49 | - name: kubernetes-cloudflare-sync 50 | image: kieranajp/kubernetes-cloudflare-sync:latest 51 | args: 52 | - --dns-name=k8s.kieranajp.uk 53 | env: 54 | - name: CF_API_KEY 55 | valueFrom: 56 | secretKeyRef: 57 | name: cloudflare 58 | key: api-key 59 | - name: CF_API_EMAIL 60 | valueFrom: 61 | secretKeyRef: 62 | name: cloudflare 63 | key: email 64 | resources: 65 | limits: 66 | memory: "50Mi" 67 | cpu: "100m" 68 | -------------------------------------------------------------------------------- /kubernetes-config/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: php-app 5 | -------------------------------------------------------------------------------- /kubernetes-config/nginx-configmap.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: nginx-config 5 | data: 6 | nginx.conf: | 7 | events { 8 | } 9 | http { 10 | server { 11 | listen 80 default_server; 12 | listen [::]:80 default_server; 13 | 14 | # Set nginx to serve files from the shared volume! 15 | root /var/www/html; 16 | server_name _; 17 | 18 | index index.php; 19 | 20 | location / { 21 | include /etc/nginx/mime.types; 22 | try_files $uri /index.php?$query_string; 23 | } 24 | 25 | location = /favicon.ico { log_not_found off; access_log off; } 26 | location = /robots.txt { access_log off; log_not_found off; } 27 | 28 | sendfile off; 29 | 30 | client_max_body_size 100m; 31 | 32 | location ~ \.php$ { 33 | include fastcgi_params; 34 | fastcgi_index index.php; 35 | fastcgi_param REQUEST_METHOD $request_method; 36 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 37 | fastcgi_pass 127.0.0.1:9000; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /kubernetes-config/php-app-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: php-app 5 | labels: 6 | app: php-app 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: php-app 12 | template: 13 | metadata: 14 | labels: 15 | app: php-app 16 | spec: 17 | volumes: 18 | # Create the shared files volume to be used in both pods 19 | - name: shared-files 20 | emptyDir: {} 21 | 22 | # Add the ConfigMap we declared above as a volume for the pod 23 | - name: nginx-config-volume 24 | configMap: 25 | name: nginx-config 26 | containers: 27 | - image: gcr.io/test-project-218312/php-app:latest 28 | name: php-app 29 | volumeMounts: 30 | - name: shared-files 31 | mountPath: /var/www/html 32 | # After the container has started, copy the PHP files from this 33 | # container's local filesystem to the shared volume, which is 34 | # mounted at /var/www/html. 35 | lifecycle: 36 | postStart: 37 | exec: 38 | command: ["/bin/sh", "-c", "cp -r /server/http/public/. /var/www/html"] 39 | 40 | - image: nginx:1.15.3-alpine 41 | name: nginx 42 | volumeMounts: 43 | - name: shared-files 44 | mountPath: /var/www/html 45 | - name: nginx-config-volume 46 | mountPath: /etc/nginx/nginx.conf 47 | subPath: nginx.conf 48 | 49 | --- 50 | 51 | kind: Service 52 | apiVersion: v1 53 | metadata: 54 | name: php-app 55 | spec: 56 | selector: 57 | app: php-app 58 | ports: 59 | - protocol: TCP 60 | port: 8080 61 | targetPort: 80 62 | -------------------------------------------------------------------------------- /public/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | justify-content: center; 4 | background-color: hsla(31,15%,50%,.1); 5 | } 6 | 7 | main { 8 | margin-top: 20vh; 9 | font-family: sans-serif; 10 | font-size: 1.5rem; 11 | font-weight: bold; 12 | padding: 1rem; 13 | max-width: 50%; 14 | background-color: hsla(31,15%,50%,.1); 15 | } 16 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hi 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | --------------------------------------------------------------------------------