Erwin Müller

Mon­i­tor­ing Word­Press with Prometheus in a Ku­ber­netes Cluster

In­tro­duc­tion

Grafana Dash­board for Word­Press Metrics

This ar­ti­cle will de­scribe how to mon­i­tor a Word­Press in­stal­la­tion with Prometheus. The set­up re­quires a work­ing Ku­ber­netes clus­ter with the Prometheus Op­er­a­tor in­stalled. This ar­ti­cle will de­scribe ba­sic three use cases.

Use Cas­es

Prometheus met­ric use cases

Word­Press Metrics

We want to see ba­sic met­rics about out site, like how many posts are pub­lished, how many com­ments there are on our site and how many users are registered.

Site La­ten­cy

We want to see the site la­ten­cy in mil­lisec­onds to our site.

Down Alert­ing

We want to be in­formed if our site goes of­fline for what­ev­er reasons.

Met­rics Projects

Sur­pris­ing­ly, there are not many Prometheus mon­i­tor­ing plu­g­ins for Word­Press. A search in Google shows just four re­sults. Here is a list of projects that we will use for our use case.

https://​github​.com/​k​o​t​s​i​s​/​w​o​r​d​p​r​e​s​s​_​e​x​p​o​r​ter

This project pro­vides Word­Press met­rics for Prometheus by read­ing di­rect­ly the MySQL data­base and is writ­ten in Go. This project is in­ter­est­ing be­cause we can learn some ba­sic Go and we will use it to cre­ate a Dock­er con­tain­er that we run along­side Word­Press in our clus­ter to pro­vide met­rics to Prometheus.

https://​github​.com/​o​r​i​g​a​m​a​/​w​o​r​d​p​r​e​s​s​-​e​x​p​o​r​t​e​r​-​p​r​o​m​e​t​h​eus

This is a very sim­ple Word­Press plu­g­in that can pro­vide met­rics to Prometheus. We will al­so use this plu­g­in be­cause then we can have the la­ten­cy of our Word­Press site in ad­di­tion to the usu­al Word­Press met­rics. But more on that later.

Here are some projects that are not use­ful for our use case, but are men­tioned for completeness.

https://​github​.com/​P​e​t​e​r​B​o​o​k​e​r​/​w​p​-​p​r​o​m​p​r​ess

The au­thor writes that it sup­pose to be a Word­Press Prometheus client, but the last com­mit is still just “ini­tial file struc­ture” one year ego. My guess is that this project is not de­vel­oped anymore.

https://​de​.word​press​.org/​p​l​u​g​i​n​s​/​w​o​o​-​p​r​o​m​e​t​h​e​u​s​-​m​e​t​r​i​cs/

This is a Word­Press plu­g­in that pro­vides met­rics specif­i­cal­ly for Woocom­merce. This is not what we want here of course.

wordpress_exporter

https://​github​.com/​d​e​v​e​n​t​/​w​o​r​d​p​r​e​s​s​_​e​x​p​o​r​ter

https://​cloud​.dock​er​.com/​u​/​e​r​w​i​n​8​2​/​r​e​p​o​s​i​t​o​r​y​/​d​o​c​k​e​r​/​e​r​w​i​n​8​2​/​w​o​r​d​p​r​e​s​s​_​e​x​p​o​r​ter

The “wordpress_exporter” project will pro­vide ba­sic Word­Press met­rics by read­ing the MySQL data­base and it will cov­er our first use case. It can not give us the site la­ten­cy since the “wordpress_exporter” runs as a sep­a­rate con­tain­er and for the same rea­son it can not give us the down alert­ing. As the first step I have forged the orig­i­nal project on Github and cre­at­ed a Dock­er­file for it. We set­up Dock­er Hub to cre­ate au­to­mat­ic builds for it. For that I have fol­lowed the guide pro­vid­ed: Build­ing Dock­er Con­tain­ers for Go Ap­pli­ca­tions.

The re­sult is a Dock­er im­age that will run a con­tain­er for the Go “wordpress_exporter”. It will pro­vide a met­ric at the URL http://wordpress-exporter:8888/metrics.

Now we can de­ploy the “wordpress_exporter” on our Ku­ber­netes cluster.

---

apiVersion: v1
kind: Service
metadata:
  name: wordpress-exporter
  namespace: www-muellerpublic-de
  labels:
    app: wordpress-exporter
    tier: metrics
spec:
  ports:
  - name: "metrics"
    port: 8888
    targetPort: 8888
  selector:
    app: wordpress-exporter

---

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: wordpress-exporter
  namespace: www-muellerpublic-de
  labels:
    app: wordpress-exporter
    tier: metrics
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress-exporter
      tier: metrics
  template:
    metadata:
      labels:
        app: wordpress-exporter
        tier: metrics
    spec:
      containers:
      - image: erwin82/wordpress_exporter:latest
        name: wordpress-exporter
        env:
        - name: WORDPRESS_DB_NAME
          value: "wordpressdb"
        - name: WORDPRESS_DB_USER
          value: "wordpress"
        - name: WORDPRESS_DB_PASSWORD
          value: "wordpress1234"
        - name: WORDPRESS_DB_HOST
          value: "db"
        - name: WORDPRESS_DB_PORT
          value: "3306"
        ports:
        - containerPort: 8888
          name: "metrics"
      restartPolicy: Always

---

We will cre­ate a new Promethus Ser­vice­Mon­i­tor for the Prometheus operator.

[python title=“sm.yaml”]
apiVer­sion: mon​i​tor​ing​.core​os​.com/v1
kind: ServiceMonitor
metadata:
name: wordpress-exporter
name­space: www-muellerpublic-de
spec:
selector:
matchLabels:
app: wordpress-exporter
endpoints:
 — port: metrics

[/python]

Since the Promethus serv­er runs in the name­space “mon­i­tor­ing” we need to cre­ate RoleBind­ing and a Role to al­low the serv­er ac­cess to the name­space where our Word­Press de­ploy­ment is.

[python title=“role.yaml”]

apiVer­sion: rbac​.au​tho​riza​tion​.k8s​.io/v1
kind: RoleBinding
metadata:
name: prometheus-k8s
name­space: www-muellerpublic-de
roleRef:
api­Group: rbac​.au​tho​riza​tion​.k8s​.io
kind: Role
name: prometheus-k8s
subjects:
 — kind: ServiceAccount
name: prometheus-k8s
name­space: monitoring

apiVer­sion: rbac​.au​tho​riza​tion​.k8s​.io/v1
kind: Role
metadata:
name: prometheus-k8s
name­space: www-muellerpublic-de
rules:
 — apiGroups:
 — “”
resources:
 — services
 — endpoints
 — pods
verbs:
 — get
 — list
 — watch

[/python]

If every­thing was cor­rect­ly de­ployed then we should now see a new tar­get in our Prometheus server.

http://prometheus/targets

The “wordpress_exporter” ex­ports the fol­low­ing metrics:

wp_num_comments_metric{endpoint=“metrics”,instance=“10.244.1.40:8888”,job=“wordpress-exporter”,namespace=“www-muellerpublic-de”,pod=“wordpress-exporter-5f647597-drvzf”,service=“wordpress-exporter”}

wp_num_posts_metric{endpoint=“metrics”,instance=“10.244.1.40:8888”,job=“wordpress-exporter”,namespace=“www-muellerpublic-de”,pod=“wordpress-exporter-5f647597-drvzf”,service=“wordpress-exporter”}

wp_num_users_metric{endpoint=“metrics”,instance=“10.244.1.40:8888”,job=“wordpress-exporter”,namespace=“www-muellerpublic-de”,pod=“wordpress-exporter-5f647597-drvzf”,service=“wordpress-exporter”}

origa­ma/­word­press-ex­porter-prometheus

The project is a Word­Press plu­g­in that pro­vides met­rics un­der the end­point http://wordpress/wp-json/metrics and we will use the Prometheus Ad­di­tion­al Scrape Con­fig­u­ra­tion to add the end­point to our Prometheus server.The in­stal­la­tion is very sim­ple. Just down­load the zip archive some­where and use Word­Press to in­stall the archive as a new plu­g­in. The plu­g­in will use the Word­Press API to pro­vide ba­sic Word­Press met­rics like num­ber of posts, com­ments and users. What im­por­tant for us is that the plu­g­ins runs in­side Word­Press so we can use it to re­turn the site la­ten­cy time and cre­ate an alert if the site be­comes unreachable.

Af­ter the in­stal­la­tion of the Word­Press plu­g­in we add the ad­di­tion­al scrape con­fig­u­ra­tion. For that we cre­ate the file “prometheus-additional.yaml” with the fol­low­ing content.

[python title=“prometheus-additional.yaml”]
 — job_name: “www-mueller­pub­lic-de”
static_configs:
 — tar­gets: [“www​.mueller​pub​lic​.de”, “www​.mueller​-pub​lic​.de”]
scrape_interval: “30s”
metrics_path: “/wp-json/­met­rics”
scheme: “https”

[/python]

Now we cre­ate a Ku­ber­netes se­cret from this file.

kubectl create secret generic additional-scrape-configs --from-file=prometheus-additional.yaml --dry-run -oyaml > additional-scrape-configs.yaml

We de­ploy it in­to the “mon­i­tor­ing” namespace.

kubectl -n monitoring apply -f additional-scrape-configs.yaml

We ed­it the Prometheus cus­tom re­source de­f­i­n­i­tion to ref­er­ence our se­cret and add the fol­low­ing snippet.

kubectl -n monitoring edit prometheus k8s

[python title=“prometheus”]
spec:
additionalScrapeConfigs:
key: prometheus-additional.yaml
name: additional-scrape-configs

[/python]

We should now have an ad­di­tion­al scrape tar­get in our Prometheus server.

http://prometheus/targets

The plu­g­in ex­ports sim­i­lar met­rics as the “wordpress_exporter” container.

Grafana

Af­ter we have con­fig­ures our scrape tar­gets we can now set­up a nice Grafana dash­board to see the met­rics and to be in­formed if our Word­Press site be­comes un­reach­able for some rea­son. I have pre­pared a Grafana dash­board for that. It re­quires Grafana 6 and a Prometheus serv­er as the da­ta source.

[text title=“WordPress ex­porter dashboard-1558274559998.json”]
{
“__inputs”: [
{
“name”: “DS_PROMETHEUS”,
“la­bel”: “prometheus”,
“de­scrip­tion”: “”,
“type”: “data­source”,
“plug­inId”: “prometheus”,
“plug­in­Name”: “Prometheus”
}
],
“__requires”: [
{
“type”: “grafana”,
“id”: “grafana”,
“name”: “Grafana”,
“ver­sion”: “6.0.1”
},
{
“type”: “pan­el”,
“id”: “graph”,
“name”: “Graph”,
“ver­sion”: “5.0.0”
},
{
“type”: “data­source”,
“id”: “prometheus”,
“name”: “Prometheus”,
“ver­sion”: “5.0.0”
},
{
“type”: “pan­el”,
“id”: “ta­ble”,
“name”: “Ta­ble”,
“ver­sion”: “5.0.0”
}
],
“an­no­ta­tions”: {
“list”: [
{
“builtIn”: 1,
“data­source”: “– Grafana –”,
“en­able”: true,
“hide”: true,
“icon­Col­or”: “rgba(0, 211, 255, 1)”,
“name”: “An­no­ta­tions & Alerts”,
“type”: “dash­board”
}
]
},
“ed­itable”: true,
“gnetId”: null,
“graph­Tooltip”: 0,
“id”: null,
“it­er­a­tion”: 1558268165521,
“links”: [],
“pan­els”: [
{
“alias­Col­ors”: {},
“bars”: false,
“dash­Length”: 10,
“dash­es”: false,
“data­source”: “${DS_PROMETHEUS}”,
“fill”: 1,
“grid­Pos”: {
“h”: 8,
“w”: 12,
“x”: 0,
“y”: 0
},
“id”: 14,
“in­ter­val”: “”,
“leg­end”: {
“avg”: true,
“cur­rent”: false,
“max”: false,
“min”: false,
“show”: true,
“to­tal”: false,
“val­ues”: true
},
“lines”: true,
“linewidth”: 1,
“links”: [],
“null­Point­Mode”: “null”,
“pace­Length”: 10,
“per­cent­age”: false,
“pointra­dius”: 2,
“points”: false,
“ren­der­er”: “flot”,
“se­riesOver­rides”: [],
“stack”: false,
“stepped­Line”: false,
“tar­gets”: [
{
“ex­pr”: “avg_over_time(scrape_duration_seconds{job=\”$namespace\”}[30m])”,
“for­mat”: “time_series”,
“in­ter­val”: “”,
“in­ter­val­Fac­tor”: 1,
“leg­end­For­mat”: “{{in­stance}}”,
“re­fId”: “A”
}
],
“thresh­olds”: [],
“time­From”: null,
“timeRe­gions”: [],
“timeShift”: null,
“ti­tle”: “Scrape Duration”,
“tooltip”: {
“shared”: true,
“sort”: 0,
“value_type”: “in­di­vid­ual”
},
“type”: “graph”,
“xax­is”: {
“buck­ets”: null,
“mode”: “time”,
“name”: null,
“show”: true,
“val­ues”: []
},
“yax­es”: [
{
“for­mat”: “s”,
“la­bel”: null,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
},
{
“for­mat”: “short”,
“la­bel”: null,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
}
],
“yax­is”: {
“align”: false,
“align­Lev­el”: null
}
},
{
“ca­cheTime­out”: null,
“columns”: [
{
“text”: “Cur­rent”,
“val­ue”: “cur­rent”
}
],
“data­source”: “${DS_PROMETHEUS}”,
“font­Size”: “100%”,
“grid­Pos”: {
“h”: 8,
“w”: 7,
“x”: 12,
“y”: 0
},
“id”: 12,
“links”: [],
“pa­ge­Size”: null,
“scroll”: true,
“show­Head­er”: true,
“sort”: {
“col”: 0,
“de­sc”: true
},
“styles”: [
{
“alias”: “Up”,
“col­or­Mode”: “cell”,
“col­ors”: [
“rgba(245, 54, 54, 0.9)”,
“rgba(237, 129, 40, 0.89)”,
“rgba(50, 172, 45, 0.97)”
],
“dec­i­mals”: 0,
“pat­tern”: “Cur­rent”,
“thresh­olds”: [
“0”,
“1”
],
“type”: “num­ber”,
“unit”: “short”
},
{
“alias”: “In­stance”,
“col­or­Mode”: null,
“col­ors”: [
“rgba(245, 54, 54, 0.9)”,
“rgba(237, 129, 40, 0.89)”,
“rgba(50, 172, 45, 0.97)”
],
“date­For­mat”: “YYYY-MM-DD HH:mm:ss”,
“dec­i­mals”: 2,
“map­ping­Type”: 1,
“pat­tern”: “Met­ric”,
“san­i­tize”: false,
“thresh­olds”: [],
“type”: “string”,
“unit”: “short”,
“val­ueMaps”: []
}
],
“tar­gets”: [
{
“ex­pr”: “up{job=\”$namespace\”}”,
“for­mat”: “time_series”,
“in­ter­val­Fac­tor”: 1,
“leg­end­For­mat”: “”,
“re­fId”: “A”
}
],
“time­From”: null,
“timeShift”: null,
“ti­tle”: “Up”,
“trans­form”: “timeseries_aggregations”,
“type”: “ta­ble”
},
{
“grid­Pos”: {
“h”: 1,
“w”: 24,
“x”: 0,
“y”: 8
},
“id”: 8,
“ti­tle”: “Sta­tis­tics”,
“type”: “row”
},
{
“alias­Col­ors”: {},
“bars”: true,
“ca­cheTime­out”: null,
“dash­Length”: 10,
“dash­es”: false,
“data­source”: “${DS_PROMETHEUS}”,
“dec­i­mals”: 0,
“fill”: 1,
“grid­Pos”: {
“h”: 9,
“w”: 7,
“x”: 0,
“y”: 9
},
“id”: 6,
“leg­end”: {
“avg”: false,
“cur­rent”: false,
“max”: false,
“min”: false,
“show”: true,
“to­tal”: false,
“val­ues”: false
},
“lines”: true,
“linewidth”: 1,
“links”: [],
“null­Point­Mode”: “null”,
“pace­Length”: 10,
“per­cent­age”: false,
“pointra­dius”: 2,
“points”: false,
“ren­der­er”: “flot”,
“se­riesOver­rides”: [],
“stack”: false,
“stepped­Line”: true,
“tar­gets”: [
{
“ex­pr”: “wp_num_comments_metric{namespace=\”$namespace\”}”,
“for­mat”: “time_series”,
“in­stant”: false,
“in­ter­val”: “”,
“in­ter­val­Fac­tor”: 10,
“leg­end­For­mat”: “$name­space”,
“re­fId”: “A”
}
],
“thresh­olds”: [],
“time­From”: null,
“timeRe­gions”: [],
“timeShift”: null,
“ti­tle”: “Num­ber of comments”,
“tooltip”: {
“shared”: true,
“sort”: 0,
“value_type”: “in­di­vid­ual”
},
“type”: “graph”,
“xax­is”: {
“buck­ets”: null,
“mode”: “time”,
“name”: null,
“show”: true,
“val­ues”: []
},
“yax­es”: [
{
“dec­i­mals”: 0,
“for­mat”: “short”,
“la­bel”: “”,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
},
{
“dec­i­mals”: null,
“for­mat”: “short”,
“la­bel”: “”,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
}
],
“yax­is”: {
“align”: false,
“align­Lev­el”: null
}
},
{
“alias­Col­ors”: {},
“bars”: true,
“ca­cheTime­out”: null,
“dash­Length”: 10,
“dash­es”: false,
“data­source”: “${DS_PROMETHEUS}”,
“fill”: 1,
“grid­Pos”: {
“h”: 9,
“w”: 6,
“x”: 7,
“y”: 9
},
“id”: 2,
“leg­end”: {
“avg”: false,
“cur­rent”: false,
“max”: false,
“min”: false,
“show”: true,
“to­tal”: false,
“val­ues”: false
},
“lines”: true,
“linewidth”: 1,
“links”: [],
“null­Point­Mode”: “null”,
“pace­Length”: 10,
“per­cent­age”: false,
“pointra­dius”: 2,
“points”: false,
“ren­der­er”: “flot”,
“se­riesOver­rides”: [],
“stack”: false,
“stepped­Line”: true,
“tar­gets”: [
{
“ex­pr”: “wp_num_users_metric{namespace=\”$namespace\”}”,
“for­mat”: “time_series”,
“in­ter­val”: “”,
“in­ter­val­Fac­tor”: 10,
“leg­end­For­mat”: “$name­space”,
“re­fId”: “A”
}
],
“thresh­olds”: [],
“time­From”: null,
“timeRe­gions”: [],
“timeShift”: null,
“ti­tle”: “Word­Press num­ber of reg­is­tered users”,
“tooltip”: {
“shared”: true,
“sort”: 0,
“value_type”: “in­di­vid­ual”
},
“type”: “graph”,
“xax­is”: {
“buck­ets”: null,
“mode”: “time”,
“name”: null,
“show”: true,
“val­ues”: []
},
“yax­es”: [
{
“dec­i­mals”: 0,
“for­mat”: “short”,
“la­bel”: “”,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
},
{
“for­mat”: “short”,
“la­bel”: null,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
}
],
“yax­is”: {
“align”: false,
“align­Lev­el”: null
}
},
{
“alias­Col­ors”: {},
“bars”: true,
“dash­Length”: 10,
“dash­es”: false,
“data­source”: “${DS_PROMETHEUS}”,
“fill”: 1,
“grid­Pos”: {
“h”: 9,
“w”: 6,
“x”: 13,
“y”: 9
},
“id”: 4,
“leg­end”: {
“avg”: false,
“cur­rent”: false,
“max”: false,
“min”: false,
“show”: true,
“to­tal”: false,
“val­ues”: false
},
“lines”: true,
“linewidth”: 1,
“links”: [],
“null­Point­Mode”: “null”,
“pace­Length”: 10,
“per­cent­age”: false,
“pointra­dius”: 5,
“points”: false,
“ren­der­er”: “flot”,
“se­riesOver­rides”: [],
“space­Length”: 10,
“stack”: false,
“stepped­Line”: true,
“tar­gets”: [
{
“ex­pr”: “wp_num_posts_metric{namespace=\”$namespace\”}”,
“for­mat”: “time_series”,
“in­ter­val”: “”,
“in­ter­val­Fac­tor”: 10,
“leg­end­For­mat”: “$name­space”,
“re­fId”: “A”
}
],
“thresh­olds”: [],
“time­From”: null,
“timeRe­gions”: [],
“timeShift”: null,
“ti­tle”: “Num­ber of Word­Press posts”,
“tooltip”: {
“shared”: true,
“sort”: 0,
“value_type”: “in­di­vid­ual”
},
“type”: “graph”,
“xax­is”: {
“buck­ets”: null,
“mode”: “time”,
“name”: null,
“show”: true,
“val­ues”: []
},
“yax­es”: [
{
“dec­i­mals”: 0,
“for­mat”: “short”,
“la­bel”: “”,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
},
{
“for­mat”: “short”,
“la­bel”: null,
“log­Base”: 1,
“max”: null,
“min”: null,
“show”: true
}
],
“yax­is”: {
“align”: false,
“align­Lev­el”: null
}
}
],
“re­fresh”: “5m”,
“schemaVer­sion”: 18,
“style”: “dark”,
“tags”: [],
“tem­plat­ing”: {
“list”: [
{
“al­l­Val­ue”: null,
“cur­rent”: {},
“data­source”: “${DS_PROMETHEUS}”,
“de­f­i­n­i­tion”: “label_values(wp_users_total, job)”,
“hide”: 0,
“in­cludeAll”: false,
“la­bel”: “Name­space”,
“mul­ti”: false,
“name”: “name­space”,
“op­tions”: [],
“query”: “label_values(wp_users_total, job)”,
“re­fresh”: 1,
“regex”: “”,
“skipUrl­Sync”: false,
“sort”: 0,
“tag­Val­ues­Query”: “”,
“tags”: [],
“tags­Query”: “”,
“type”: “query”,
“use­Tags”: false
}
]
},
“time”: {
“from”: “now-24h”,
“to”: “now”
},
“timepick­er”: {
“refresh_intervals”: [
“5s”,
“10s”,
“30s”,
“1m”,
“5m”,
“15m”,
“30m”,
“1h”,
“2h”,
“1d
],
“time_options”: [
“5m”,
“15m”,
“1h”,
“6h”,
“12h”,
“24h”,
“2d”,
“7d”,
“30d
]
},
“time­zone”: “”,
“ti­tle”: “Word­Press ex­porter dashboard”,
“uid”: “qtFzy1dik”,
“ver­sion”: 12
}

[/text]

Leave a Reply

Your email address will not be published.