diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..b4710f9f4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.build/ + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..2abd919ae4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "k8s/vendor/marketplace-tools"] + path = k8s/vendor/marketplace-tools + url = https://github.com/GoogleCloudPlatform/marketplace-k8s-app-tools diff --git a/k8s/vendor/marketplace-tools b/k8s/vendor/marketplace-tools new file mode 160000 index 0000000000..ada1168d43 --- /dev/null +++ b/k8s/vendor/marketplace-tools @@ -0,0 +1 @@ +Subproject commit ada1168d43e5b795a66798476c0aff3ad726dc9d diff --git a/k8s/wordpress/Makefile b/k8s/wordpress/Makefile new file mode 100644 index 0000000000..ab7b89518b --- /dev/null +++ b/k8s/wordpress/Makefile @@ -0,0 +1,34 @@ +APP_NAME = wordpress +AGENT_REPO ?= ../../../ubbagent + +include ../vendor/marketplace-tools/gcloud.Makefile + +include ../vendor/marketplace-tools/crd.Makefile +include ../vendor/marketplace-tools/app.Makefile + +app/build:: .build/deployer .build/init .build/marketplace-ubbagent + +.build/deployer: deployer/* manifest/* $(MARKETPLACE_BASE_BUILD)/deployer-kubectl $(MARKETPLACE_BASE_BUILD)/controller $(APP_BUILD)/registry_prefix | app/setup + docker build \ + --build-arg MARKETPLACE_REGISTRY="$(MARKETPLACE_REGISTRY)" \ + --tag "$(APP_DEPLOYER_IMAGE)" \ + -f deployer/Dockerfile \ + . + gcloud docker -- push "$(APP_DEPLOYER_IMAGE)" + @touch "$@" + +.build/init: init/* $(APP_BUILD)/registry_prefix | app/setup + cd init \ + && docker build \ + --tag "$(APP_REGISTRY)/init" \ + . + gcloud docker -- push "$(APP_REGISTRY)/init" + @touch "$@" + +.build/marketplace-ubbagent: $(AGENT_REPO)/* $(AGENT_REPO)/**/* $(MARKETPLACE_BASE_BUILD)/registry_prefix | base/setup + cd "$(AGENT_REPO)" \ + && docker build \ + --tag "$(MARKETPLACE_REGISTRY)/ubbagent" \ + . + gcloud docker -- push "$(MARKETPLACE_REGISTRY)/ubbagent" + @touch "$@" diff --git a/k8s/wordpress/README.md b/k8s/wordpress/README.md new file mode 100644 index 0000000000..fad9a2ad7d --- /dev/null +++ b/k8s/wordpress/README.md @@ -0,0 +1,62 @@ +*This directory contains an example Kubernetes application (app) based on +WordPress for the purpose of demonstrating app integration with +Google Cloud Marketplace. **Not intended for actual use!*** + +*Content below is intended as a template for end-user documentation. Work in +progress.* + +# Overview + +WordPress is a free and open-source content management system (CMS) based on PHP +and MySQL... + +# Installation + +## Quick install with Google Cloud Marketplace + +Get up and running with a few clicks! Install this WordPress app to a +Google Kubernetes Engine cluster using Google Cloud Marketplace. Follow the +on-screen instructions: +*TODO: link to solution details page* + +## Command line instructions + +Follow these instructions to install WordPress from the command line. + +### Prerequisites + +- Setup cluster + - Permissions +- Setup kubectl +- Install Application Resource +- Acquire License + +*TODO: add details above* + +### Commands + +Set environment variables (modify if necessary): +``` +export APP_INSTANCE_NAME=wordpress-1 +export NAMESPACE=default +``` + +Expand manifest template: +``` +cat manifest/* | envsubst > expanded.yaml +``` + +Run kubectl: +``` +kubectl apply -f expanded.yaml +``` + +*TODO: fix instructions* + +# Backups + +*TODO: instructions for backups* + +# Upgrades + +*TODO: instructions for upgrades* diff --git a/k8s/wordpress/deployer/Dockerfile b/k8s/wordpress/deployer/Dockerfile new file mode 100644 index 0000000000..f2bf78f0d6 --- /dev/null +++ b/k8s/wordpress/deployer/Dockerfile @@ -0,0 +1,4 @@ +ARG MARKETPLACE_REGISTRY +FROM ${MARKETPLACE_REGISTRY}/deployer_kubectl_base + +ADD manifest/* /data/manifest/ diff --git a/k8s/wordpress/init/Dockerfile b/k8s/wordpress/init/Dockerfile new file mode 100644 index 0000000000..903bdecf13 --- /dev/null +++ b/k8s/wordpress/init/Dockerfile @@ -0,0 +1,7 @@ +FROM launcher.gcr.io/google/debian9 + +COPY metering.php.tmpl / +COPY agent-config.yaml / +COPY init.sh / + +CMD ["/init.sh"] diff --git a/k8s/wordpress/init/agent-config.yaml b/k8s/wordpress/init/agent-config.yaml new file mode 100644 index 0000000000..f38bafdb7c --- /dev/null +++ b/k8s/wordpress/init/agent-config.yaml @@ -0,0 +1,25 @@ +metrics: +- name: requests + type: int + aggregation: + bufferSeconds: 60 + endpoints: + - name: disk +- name: instance_time + type: int + aggregation: + bufferSeconds: 60 + endpoints: + - name: disk +endpoints: +- name: disk + disk: + reportDir: $AGENT_REPORT_DIR + expireSeconds: 3600 +sources: +- name: instance_time + heartbeat: + metric: instance_time + intervalSeconds: 10 + value: + int64Value: 10 diff --git a/k8s/wordpress/init/init.sh b/k8s/wordpress/init/init.sh new file mode 100755 index 0000000000..7d0502601c --- /dev/null +++ b/k8s/wordpress/init/init.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +if [ -z "$AGENT_LOCAL_PORT" ]; then + echo "AGENT_LOCAL_PORT environment variable must be set" + exit 1 +fi + +if [ ! -d "/var/www/html" ]; then + echo "/var/www/html directory must be mounted" + exit 1 +fi + +if [ ! -d "/etc/ubbagent" ]; then + echo "/etc/ubbagent directory must be mounted" + exit 1 +fi + +# Expand and copy Wordpress metering plugin. +mkdir -p /var/www/html/wp-content/mu-plugins +sed "s/%%AGENT_LOCAL_PORT%%/$AGENT_LOCAL_PORT/g" /metering.php.tmpl > /var/www/html/wp-content/mu-plugins/metering.php + +# Copy the metering agent config. +cp /agent-config.yaml /etc/ubbagent/config.yaml diff --git a/k8s/wordpress/init/metering.php.tmpl b/k8s/wordpress/init/metering.php.tmpl new file mode 100644 index 0000000000..2456f05a97 --- /dev/null +++ b/k8s/wordpress/init/metering.php.tmpl @@ -0,0 +1,159 @@ + 'requests', + 'startTime' => $now, + 'endTime' => $now, + 'value' => array( 'int64Value' => 1 ) + ); + $args = array( + 'headers' => array( 'Content-Type' => 'application/json' ), + 'body' => json_encode( $body ) + ); + + $response = wp_remote_post( report_url(), $args); + $response_code = wp_remote_retrieve_response_code( $response ); + + return $response_code == 200; +} + +/** + * A 'wp' action that reports requests for singular post/page views. + */ +function handle_view() { + if (is_singular()) { + report_request(); + } +} + +/** + * Renders the Metering Status widget. + */ +function usage_metering_status_widget_display() { + $response = wp_remote_get(status_url()); + $error = ''; + $status = NULL; + if (is_wp_error($request)) { + $error = 'Cannot reach agent'; + } else { + $body = wp_remote_retrieve_body($response); + $status = json_decode($body, true); + if ($status["currentFailureCount"] > 0) { + $error = 'Agent is failing to report usage'; + } + $lastSuccess = DateTime::createFromFormat('Y-m-d\TH:i:s+', $status['lastReportSuccess']); + $now = new DateTime(); + $successInterval = $now->diff($lastSuccess)->format('%ad %hh %im %ss'); + } + + if ($error == '') { + $current_status = 'SUCCESS'; + } else { + $current_status = "FAILURE: $error"; + } + + // Widget HTML + ?> +

Reporting Health

+

+ + +

Status Data

+ + + + + + + + + + + + + +
Last report success ago
Current failures
Total failures
+ + diff --git a/k8s/wordpress/manifest/controller.yaml.template b/k8s/wordpress/manifest/controller.yaml.template new file mode 100644 index 0000000000..2347535b2e --- /dev/null +++ b/k8s/wordpress/manifest/controller.yaml.template @@ -0,0 +1,76 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: $APP_INSTANCE_NAME-controller-sa + namespace: $NAMESPACE +--- +# This role is what an application specific controller would +# typically need. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: $APP_INSTANCE_NAME-controller-approle + namespace: $NAMESPACE +rules: +- apiGroups: ['marketplace.cloud.google.com'] + resources: ['applications'] + verbs: ['*'] +- apiGroups: [''] + resources: ['events'] + verbs: ['*'] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: $APP_INSTANCE_NAME-controller-apprb + namespace: $NAMESPACE +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: $APP_INSTANCE_NAME-controller-approle +subjects: +- kind: ServiceAccount + name: $APP_INSTANCE_NAME-controller-sa +--- +# We need this binding because the controller currently +# needs to assign owner references. This functionality +# will be replaced by the CRD controller. +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: $APP_INSTANCE_NAME-controller-editrb + namespace: $NAMESPACE +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + # This role is intended for the deployer container. See comment + # above about this binding. + name: $APP_INSTANCE_NAME-deployer-role +subjects: +- kind: ServiceAccount + name: $APP_INSTANCE_NAME-controller-sa +--- +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: $APP_INSTANCE_NAME-controller + labels: &MysqlDeploymentLabels + app: $APP_INSTANCE_NAME + component: wordpress-controller +spec: + replicas: 1 + selector: + matchLabels: *MysqlDeploymentLabels + template: + metadata: + labels: *MysqlDeploymentLabels + spec: + serviceAccountName: $APP_INSTANCE_NAME-controller-sa + containers: + - image: $MARKETPLACE_REGISTRY/controller + name: controller + env: + - name: APP_INSTANCE_NAME + value: "$APP_INSTANCE_NAME" + - name: NAMESPACE + value: "$NAMESPACE" diff --git a/k8s/wordpress/manifest/manifests.yaml.template b/k8s/wordpress/manifest/manifests.yaml.template new file mode 100644 index 0000000000..e64763817b --- /dev/null +++ b/k8s/wordpress/manifest/manifests.yaml.template @@ -0,0 +1,223 @@ +--- +apiVersion: marketplace.cloud.google.com/v1 +kind: Application +metadata: + annotations: + marketplace.cloud.google.com: | + {"name":"Wordpress","version":"v0.1","description":"The most popular blogging platform","url":"wordpress.org","tagline":"wordpress blog","support_info":"Community support","documentations":[{"url":"https://codex.wordpress.org/Getting_Started_with_WordPress","title":"Getting Started","description":"A quick walkthrough"}]} + ApplicationStatus: + ready: true + generation: 0 + initializers: null + name: "$APP_INSTANCE_NAME" + namespace: "$NAMESPACE" +spec: + # TODO(huyhuynh): This list "replaces" the original list created + # by the up script. As a result, the deployer service account, for example, + # is no longer owned by this application. We have to hackily list such + # non-application resources here. + # To correctly fix this, we need to use a proper merge stategy. The idea is + # that kubectl wouldn't touch the components that it doesn't manage. + # + # TODO(huyhuynh): Need a good way to do this. Listing the components + # manually like this is error-prone and especially hard when there are + # many manifest files or when they are modified. + components: + - $APP_INSTANCE_NAME-controller: + kind: Deployment + - $APP_INSTANCE_NAME-controller-sa: + kind: ServiceAccount + - $APP_INSTANCE_NAME-controller-approle: + kind: Role + - $APP_INSTANCE_NAME-controller-apprb: + kind: RoleBinding + - $APP_INSTANCE_NAME-controller-editrb: + kind: RoleBinding + - $APP_INSTANCE_NAME-mysql: + kind: Deployment + - $APP_INSTANCE_NAME-mysql-pvc: + kind: PersistentVolumeClaim + - $APP_INSTANCE_NAME-mysql-svc: + kind: Service + - $APP_INSTANCE_NAME-wordpress: + kind: Deployment + - $APP_INSTANCE_NAME-wordpress-pvc: + kind: PersistentVolumeClaim + - $APP_INSTANCE_NAME-wordpress-svc: + kind: Service + # The following shouldn't be listed here. They are not part of the + # application itself, and the solution crafter shouldn't have to worry + # about them. See TODOs above. + - $APP_INSTANCE_NAME-deployer: + kind: Job + - $APP_INSTANCE_NAME-deployer-sa: + kind: ServiceAccount + - $APP_INSTANCE_NAME-deployer-role: + apiGroup: rbac.authorization.k8s.io + kind: Role + - $APP_INSTANCE_NAME-deployer-rb: + apiGroup: rbac.authorization.k8s.io + kind: RoleBinding +--- +# TODO(huyhuynh): Change this to using StatefulSet +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: $APP_INSTANCE_NAME-mysql + labels: &MysqlDeploymentLabels + app: $APP_INSTANCE_NAME + component: wordpress-mysql +spec: + replicas: 1 + selector: + matchLabels: *MysqlDeploymentLabels + template: + metadata: + labels: *MysqlDeploymentLabels + spec: + containers: + - image: launcher.gcr.io/google/mysql5 + name: mysql + env: + - name: "MYSQL_ROOT_PASSWORD" + value: "example-password" + volumeMounts: + - name: data + mountPath: /var/lib/mysql + subPath: data + volumes: + - name: data + persistentVolumeClaim: + claimName: $APP_INSTANCE_NAME-mysql-pvc +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: $APP_INSTANCE_NAME-mysql-pvc + labels: + app: $APP_INSTANCE_NAME + component: wordpress-mysql +spec: + accessModes: [ReadWriteOnce] + storageClassName: standard + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: $APP_INSTANCE_NAME-mysql-svc + labels: + app: $APP_INSTANCE_NAME + component: wordpress-mysql +spec: + ports: + - port: 3306 + selector: + app: $APP_INSTANCE_NAME + component: wordpress-mysql + clusterIP: None +--- +# TODO(huyhuynh): Change this to using StatefulSet +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: $APP_INSTANCE_NAME-wordpress + labels: &WordpressDeploymentLabels + app: $APP_INSTANCE_NAME + component: wordpress-webserver +spec: + replicas: 1 + selector: + matchLabels: *WordpressDeploymentLabels + template: + metadata: + labels: *WordpressDeploymentLabels + spec: + initContainers: + - image: $REGISTRY/init + name: wordpress-init + env: + - name: AGENT_LOCAL_PORT + value: "6080" + volumeMounts: + - name: data + mountPath: /var/www/html + subPath: wp + - name: ubbagent-config + mountPath: /etc/ubbagent + containers: + - image: launcher.gcr.io/google/wordpress4-php5-apache + name: wordpress + env: + - name: WORDPRESS_DB_HOST + value: $APP_INSTANCE_NAME-mysql-svc + # TODO(huyhuynh): Use secrets. + - name: WORDPRESS_DB_PASSWORD + value: example-password + - name: WORDPRESS_DB_USER + value: root + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: data + mountPath: /var/www/html + subPath: wp + - image: $MARKETPLACE_REGISTRY/ubbagent + name: ubbagent + env: + - name: AGENT_CONFIG_FILE + value: /etc/ubbagent/config.yaml + - name: AGENT_LOCAL_PORT + value: "6080" + - name: AGENT_STATE_DIR + value: /var/lib/ubbagent + - name: AGENT_REPORT_DIR + value: /var/lib/ubbagent/reports + volumeMounts: + - name: ubbagent-config + mountPath: /etc/ubbagent + - name: ubbagent-state + mountPath: /var/lib/ubbagent + volumes: + - name: data + persistentVolumeClaim: + claimName: $APP_INSTANCE_NAME-wordpress-pvc + - name: ubbagent-config + emptyDir: {} + # TODO(volkman): state directory should maybe be on a PV. + - name: ubbagent-state + emptyDir: {} +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: $APP_INSTANCE_NAME-wordpress-pvc + labels: + app: $APP_INSTANCE_NAME + component: wordpress-webserver +spec: + accessModes: [ReadWriteOnce] + storageClassName: standard + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: $APP_INSTANCE_NAME-wordpress-svc + labels: + app: $APP_INSTANCE_NAME + component: wordpress-webserver +spec: + ports: + - name: http + port: 80 + targetPort: http + selector: + app: $APP_INSTANCE_NAME + component: wordpress-webserver + type: LoadBalancer