Repository: crazy-max/diun
Pull request #95 (2020-06-15 17:50:40)

Kubernetes provider

codecov[bot]: # [Codecov](https://codecov.io/gh/crazy-max/diun/pull/95?src=pr&el=h1) Report
> Merging [#95](https://codecov.io/gh/crazy-max/diun/pull/95?src=pr&el=desc) into [v4](https://codecov.io/gh/crazy-max/diun/commit/e68ba0c8bd05517357446244f1e24fe3c02f4a67&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/crazy-max/diun/pull/95/graphs/tree.svg?width=650&height=150&src=pr&token=phc5KYIjsV)](https://codecov.io/gh/crazy-max/diun/pull/95?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##               v4      #95   +/-   ##
=======================================
  Coverage   67.20%   67.20%           
=======================================
  Files           7        7           
  Lines         250      250           
=======================================
  Hits          168      168           
  Misses         55       55           
  Partials       27       27           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/crazy-max/diun/pull/95?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/crazy-max/diun/pull/95?src=pr&el=footer). Last update [e68ba0c...2e079ae](https://codecov.io/gh/crazy-max/diun/pull/95?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.examples/docker/docker-compose.yml b/.examples/docker/docker-compose.yml
new file mode 100644
index 00000000..b7981427
--- /dev/null
+++ b/.examples/docker/docker-compose.yml
@@ -0,0 +1,37 @@
+version: "3.2"
+
+services:
+  diun:
+    image: crazymax/diun:latest
+    container_name: diun
+    volumes:
+      - "./data:/data"
+      - "/var/run/docker.sock:/var/run/docker.sock"
+    environment:
+      - "TZ=Europe/Paris"
+      - "LOG_LEVEL=info"
+      - "LOG_JSON=false"
+      - "DIUN_WATCH_WORKERS=20"
+      - "DIUN_WATCH_SCHEDULE=*/30 * * * *"
+      - "DIUN_PROVIDERS_DOCKER=true"
+    labels:
+      - "diun.enable=true"
+      - "diun.watch_repo=true"
+    restart: always
+
+  cloudflared:
+    image: crazymax/cloudflared:latest
+    ports:
+      - target: 5053
+        published: 5053
+        protocol: udp
+      - target: 49312
+        published: 49312
+        protocol: tcp
+    environment:
+      - "TZ=Europe/Paris"
+      - "TUNNEL_DNS_UPSTREAM=https://1.1.1.1/dns-query,https://1.0.0.1/dns-query"
+    labels:
+      - "diun.enable=true"
+      - "diun.watch_repo=true"
+    restart: always
diff --git a/.examples/k8s/diun.yml b/.examples/k8s/diun.yml
new file mode 100644
index 00000000..9bdf2ad1
--- /dev/null
+++ b/.examples/k8s/diun.yml
@@ -0,0 +1,47 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: diun
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: diun
+  template:
+    metadata:
+      labels:
+        app: diun
+#      annotations:
+#        diun.enable: "true"
+#        diun.watch_repo: "true"
+    spec:
+      containers:
+        - name: diun
+          image: crazymax/diun:latest
+          imagePullPolicy: Always
+          env:
+            - name: TZ
+              value: "Europe/Paris"
+            - name: LOG_LEVEL
+              value: "info"
+            - name: LOG_JSON
+              value: "false"
+            - name: DIUN_WATCH_WORKERS
+              value: "20"
+            - name: DIUN_WATCH_SCHEDULE
+              value: "*/30 * * * *"
+            - name: DIUN_PROVIDERS_KUBERNETES
+              value: "true"
+          volumeMounts:
+            - mountPath: "/data"
+              name: "data"
+      restartPolicy: Always
+      volumes:
+        # Set up a data directory for gitea
+        # For production usage, you should consider using PV/PVC instead(or simply using storage like NAS)
+        # For more details, please see https://kubernetes.io/docs/concepts/storage/volumes/
+        - name: "data"
+          hostPath:
+            path: "/data"
+            type: Directory
diff --git a/.examples/k8s/nginx.yml b/.examples/k8s/nginx.yml
new file mode 100644
index 00000000..edc9b33c
--- /dev/null
+++ b/.examples/k8s/nginx.yml
@@ -0,0 +1,22 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx
+spec:
+  selector:
+    matchLabels:
+      run: nginx
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        run: nginx
+      annotations:
+        diun.enable: "true"
+        diun.watch_repo: "true"
+    spec:
+      containers:
+        - name: nginx
+          image: nginx
+          ports:
+            - containerPort: 80
diff --git a/.examples/swarm/diun.yml b/.examples/swarm/diun.yml
new file mode 100644
index 00000000..7258bf9f
--- /dev/null
+++ b/.examples/swarm/diun.yml
@@ -0,0 +1,24 @@
+version: "3.5"
+
+services:
+  diun:
+    image: crazymax/diun:latest
+    volumes:
+      - "./data:/data"
+      - "/var/run/docker.sock:/var/run/docker.sock"
+    environment:
+      - "TZ=Europe/Paris"
+      - "LOG_LEVEL=info"
+      - "LOG_JSON=false"
+      - "DIUN_WATCH_WORKERS=20"
+      - "DIUN_WATCH_SCHEDULE=*/30 * * * *"
+      - "DIUN_PROVIDERS_SWARM=true"
+    deploy:
+      mode: replicated
+      replicas: 1
+      labels:
+        - "diun.enable=true"
+        - "diun.watch_repo=true"
+      placement:
+        constraints:
+          - node.role == manager
diff --git a/.examples/swarm/nginx.yml b/.examples/swarm/nginx.yml
new file mode 100644
index 00000000..0634cf3a
--- /dev/null
+++ b/.examples/swarm/nginx.yml
@@ -0,0 +1,15 @@
+version: "3.5"
+
+services:
+  nginx:
+    image: nginx
+    ports:
+      - target: 80
+        published: 80
+        protocol: udp
+    deploy:
+      mode: replicated
+      replicas: 2
+      labels:
+        - "diun.enable=true"
+        - "diun.watch_repo=true"
diff --git a/.res/systemd/diun.service b/.examples/systemd/diun.service
similarity index 100%
rename from .res/systemd/diun.service
rename to .examples/systemd/diun.service
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 460b331f..d02addf0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,6 +21,9 @@ jobs:
   go:
     runs-on: ubuntu-latest
     steps:
+      -
+        name: Checkout
+        uses: actions/checkout@v2
       -
         name: Prepare
         id: prepare
@@ -34,8 +37,13 @@ jobs:
         with:
           go-version: 1.13
       -
-        name: Checkout
-        uses: actions/checkout@v2
+        name: Cache Go modules
+        uses: actions/cache@v2
+        with:
+          path: ~/go/pkg/mod
+          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-go-
       -
         name: GoReleaser
         uses: goreleaser/goreleaser-action@v2
@@ -102,30 +110,30 @@ jobs:
             ${TAGS} --file Dockerfile .
       -
         name: Set up Docker Buildx
-        uses: crazy-max/ghaction-docker-buildx@v2
+        uses: crazy-max/ghaction-docker-buildx@v3.1.0
       -
         name: Docker Buildx (build)
         run: |
           docker buildx build --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }}
       -
         name: Docker Login
-        if: success() && github.event_name != 'pull_request'
+        if: success() && github.event_name != 'pull_request' && endsWith(github.ref, github.repository.default_branch)
         env:
           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
         run: |
           echo "${DOCKER_PASSWORD}" | docker login --username "${{ steps.prepare.outputs.docker_username }}" --password-stdin
       -
         name: Docker Buildx (push)
-        if: success() && github.event_name != 'pull_request'
+        if: success() && github.event_name != 'pull_request' && endsWith(github.ref, github.repository.default_branch)
         run: |
           docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }}
       -
         name: Docker Check Manifest
-        if: always() && github.event_name != 'pull_request'
+        if: success() && github.event_name != 'pull_request' && endsWith(github.ref, github.repository.default_branch)
         run: |
           docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }}
       -
         name: Clear
-        if: always() && github.event_name != 'pull_request'
+        if: always() && github.event_name != 'pull_request' && endsWith(github.ref, github.repository.default_branch)
         run: |
           rm -f ${HOME}/.docker/config.json
diff --git a/.github/workflows/gosum.yml b/.github/workflows/gosum.yml
index bc393bbe..d09d2539 100644
--- a/.github/workflows/gosum.yml
+++ b/.github/workflows/gosum.yml
@@ -3,8 +3,7 @@ name: gosum
 on:
   push:
     branches:
-      - 'master'
-      - 'v*'
+      - '**'
     paths:
       - '.github/workflows/gosum.yml'
       - 'go.mod'
diff --git a/.res/compose/docker-compose.yml b/.res/compose/docker-compose.yml
deleted file mode 100644
index 59a33dc1..00000000
--- a/.res/compose/docker-compose.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-version: "3.2"
-
-services:
-  diun:
-    image: crazymax/diun:latest
-    container_name: diun
-    volumes:
-      - "./data:/data"
-      - "/var/run/docker.sock:/var/run/docker.sock"
-    environment:
-      - "TZ=Europe/Paris"
-      - "LOG_LEVEL=info"
-      - "LOG_JSON=false"
-      - "DIUN_PROVIDERS_DOCKER=true"
-      - "DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=false"
-      - "DIUN_PROVIDERS_DOCKER_WATCHSTOPPED=false"
-    labels:
-      - "diun.enable=true"
-      - "diun.watch_repo=true"
-    restart: always
diff --git a/doc/configuration.md b/doc/configuration.md
index 5e0745f4..abe4a4a1 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -97,6 +97,10 @@ providers:
     watchStopped: true
   swarm:
     watchByDefault: true
+  kubernetes:
+    namespaces:
+      - default
+      - production
   file:
     directory: ./imagesdir
 ```
@@ -157,4 +161,5 @@ You can also use the following environment variables:
 
 * [docker](providers/docker.md)
 * [swarm](providers/swarm.md)
+* [kubernetes](providers/kubernetes.md)
 * [file](providers/file.md)
diff --git a/doc/faq.md b/doc/faq.md
index 8a84113c..5695b6aa 100644
--- a/doc/faq.md
+++ b/doc/faq.md
@@ -20,7 +20,7 @@ docker-compose exec diun --test-notif
 
 ## field docker|swarm uses unsupported type: invalid
 
-If you have the error `failed to decode configuration from file: field docker uses unsupported type: invalid` that's because your `docker` or `swarm` provider is not initialized in your configuration:
+If you have the error `failed to decode configuration from file: field docker uses unsupported type: invalid` that's because your `docker`, `swarm` or `kubernetes` provider is not initialized in your configuration:
 
 ```yaml
 providers:
diff --git a/doc/providers/file.md b/doc/providers/file.md
index 7c120a8f..49fb0261 100644
--- a/doc/providers/file.md
+++ b/doc/providers/file.md
@@ -133,7 +133,7 @@ Sat, 14 Dec 2019 15:32:28 UTC INF Next run in 31 seconds (2019-12-14 15:33:00 +0
 
 ### Configuration file
 
-#### filename
+#### `filename`
 
 Defines the path to the [configuration file](#yaml-configuration-file).
 
@@ -145,7 +145,7 @@ providers:
     filename: /path/to/config/conf.yml
 ```
 
-#### directory
+#### `directory`
 
 Defines the path to the directory that contains the [configuration files](#yaml-configuration-file) (`*.yml` or `*.yaml`).
 
diff --git a/doc/providers/kubernetes.md b/doc/providers/kubernetes.md
new file mode 100644
index 00000000..6805aa47
--- /dev/null
+++ b/doc/providers/kubernetes.md
@@ -0,0 +1,214 @@
+# Kubernetes provider
+
+* [About](#about)
+* [Quick start](#quick-start)
+* [Provider configuration](#provider-configuration)
+  * [Configuration file](#configuration-file)
+  * [Environment variables](#environment-variables)
+* [Kubernetes annotations](#kubernetes-annotations)
+
+## About
+
+The Kubernetes provider allows you to analyze the pods of your Kubernetes cluster to extract images found and check for updates on the registry.
+
+## Quick start
+
+In this section we quickly go over a basic deployment using your local Kubernetes cluster.
+
+Here we use our local Kubernetes provider with a minimum configuration to analyze annotated pods (watch by default disabled).
+
+Now let's create a simple pod for Diun:
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: diun
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: diun
+  template:
+    metadata:
+      labels:
+        app: diun
+    spec:
+      containers:
+      - name: diun
+        image: crazymax/diun:latest
+        imagePullPolicy: Always
+        env:
+          - name: TZ
+            value: "Europe/Paris"
+          - name: LOG_LEVEL
+            value: "info"
+          - name: LOG_JSON
+            value: "false"
+          - name: DIUN_WATCH_WORKERS
+            value: "20"
+          - name: DIUN_WATCH_SCHEDULE
+            value: "*/30 * * * *"
+          - name: DIUN_PROVIDERS_KUBERNETES
+            value: "true"
+        volumeMounts:
+          - mountPath: "/data"
+            name: "data"
+      restartPolicy: Always
+      volumes:
+        # Set up a data directory for gitea
+        # For production usage, you should consider using PV/PVC instead(or simply using storage like NAS)
+        # For more details, please see https://kubernetes.io/docs/concepts/storage/volumes/
+      - name: "data"
+        hostPath:
+          path: "/data"
+          type: Directory
+```
+
+And another one with a simple Nginx pod:
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx
+spec:
+  selector:
+    matchLabels:
+      run: nginx
+  replicas: 2
+  template:
+    metadata:
+      labels:
+        run: nginx
+      annotations:
+        diun.enable: "true"
+        diun.watch_repo: "true"
+    spec:
+      containers:
+      - name: nginx
+        image: nginx
+        ports:
+        - containerPort: 80
+```
+
+As an example we use [nginx](https://hub.docker.com/_/nginx/) Docker image. A few [annotations](#kubernetes-annotations) are added to configure the image analysis of this pod for Diun. We can now start these 2 pods:
+
+```
+kubectl apply -f diun.yml
+kubectl apply -f nginx.yml
+```
+
+Now take a look at the logs:
+
+```
+$ kubectl logs -f -l app=diun --all-containers
+# TODO: add logs example
+```
+
+## Provider configuration
+
+### Configuration file
+
+#### `endpoint`
+
+The Kubernetes server endpoint as URL.
+
+```yaml
+providers:
+  kubernetes:
+    endpoint: "http://localhost:8080"
+```
+
+Kubernetes server endpoint as URL, which is only used when the behavior based on environment variables described below does not apply.
+
+When deployed into Kubernetes, Diun reads the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` or `KUBECONFIG` to create the endpoint.
+
+The access token is looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token` and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`. They are both provided automatically as mounts in the pod where Diun is deployed.
+
+When the environment variables are not found, Diun tries to connect to the Kubernetes API server with an external-cluster client. In which case, the endpoint is required. Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster using the granted authentication and authorization of the associated kubeconfig.
+
+#### `token`
+
+```yaml
+providers:
+  kubernetes:
+    token: "atoken"
+```
+
+Bearer token used for the Kubernetes client configuration.
+
+#### `tokenFile`
+
+Use content of secret file as bearer token if `token` not defined.
+
+```yaml
+providers:
+  kubernetes:
+    tokenFile: "/run/secrets/token"
+```
+
+#### `certAuthFilePath`
+
+Path to the certificate authority file. Used for the Kubernetes client configuration.
+
+```yaml
+providers:
+  kubernetes:
+    certAuthFilePath: "/a/ca.crt"
+```
+
+#### `tlsInsecure`
+
+Controls whether client does not verify the server's certificate chain and hostname (default `false`).
+
+```yaml
+providers:
+  kubernetes:
+    tlsInsecure: false
+```
+
+#### `namespaces`
+
+Array of namespaces to watch (default all namespaces).
+
+```yaml
+providers:
+  kubernetes:
+    namespaces:
+      - default
+      - production
+```
+
+#### `watchByDefault`
+
+Enable watch by default. If false, pods that don't have `diun.enable: "true"` annotation will be ignored (default `false`).
+
+```yaml
+providers:
+  kubernetes:
+    watchByDefault: false
+```
+
+### Environment variables
+
+* `DIUN_PROVIDERS_KUBERNETES`
+* `DIUN_PROVIDERS_KUBERNETES_ENDPOINT`
+* `DIUN_PROVIDERS_KUBERNETES_TOKEN`
+* `DIUN_PROVIDERS_KUBERNETES_TOKENFILE`
+* `DIUN_PROVIDERS_KUBERNETES_CERTAUTHFILEPATH`
+* `DIUN_PROVIDERS_KUBERNETES_TLSINSECURE`
+* `DIUN_PROVIDERS_KUBERNETES_NAMESPACES` (comma separated)
+* `DIUN_PROVIDERS_KUBERNETES_WATCHBYDEFAULT`
+
+## Kubernetes annotations
+
+You can configure more finely the way to analyze the image of your pods through Kubernetes annotations:
+
+* `diun.enable`: Set to true to enable image analysis of this pod.
+* `diun.regopts_id`: Registry options ID from [`regopts`](../configuration.md#regopts) to use.
+* `diun.watch_repo`: Watch all tags of this pod image (default `false`).
+* `diun.max_tags`: Maximum number of tags to watch if `diun.watch_repo` enabled. 0 means all of them (default `0`).
+* `diun.include_tags`: Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo`.
+* `diun.exclude_tags`: Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo`.
diff --git a/doc/providers/swarm.md b/doc/providers/swarm.md
index e1678b2e..12d167e2 100644
--- a/doc/providers/swarm.md
+++ b/doc/providers/swarm.md
@@ -36,6 +36,8 @@ services:
       - "DIUN_WATCH_SCHEDULE=*/30 * * * *"
       - "DIUN_PROVIDERS_SWARM=true"
     deploy:
+      mode: replicated
+      replicas: 1
       placement:
         constraints:
           - node.role == manager
@@ -102,8 +104,8 @@ diun_diun.1.i1l4yuiafq6y@docker-desktop    | Sat, 14 Dec 2019 16:20:02 CET INF N
 
 * `endpoint`: Server address to connect to. Local if empty.
 * `apiVersion`: Overrides the client version with the specified one.
-* `TLSCertsPath`: Path to load the TLS certificates from.
-* `TLSVerify`: Controls whether client verifies the server's certificate chain and hostname (default `true`).
+* `tlsCertsPath`: Path to load the TLS certificates from.
+* `tlsVerify`: Controls whether client verifies the server's certificate chain and hostname (default `true`).
 * `watchByDefault`: Enable watch by default. If false, services that don't have `diun.enable=true` label will be ignored (default `false`).
 
 ### Environment variables
diff --git a/go.mod b/go.mod
index 96df5e0d..902fdbd4 100644
--- a/go.mod
+++ b/go.mod
@@ -28,6 +28,9 @@ require (
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
 	gopkg.in/yaml.v2 v2.3.0
+	k8s.io/api v0.18.2
+	k8s.io/apimachinery v0.18.2
+	k8s.io/client-go v0.18.2
 )
 
 // Docker v19.03.6
diff --git a/go.sum b/go.sum
index b361da10..f0c9f5aa 100644
--- a/go.sum
+++ b/go.sum
@@ -182,6 +182,7 @@ github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6
 github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
 github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
@@ -277,6 +278,8 @@ github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv3
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
+github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -290,6 +293,7 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI=
 github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
 github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
@@ -364,6 +368,7 @@ github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBv
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -438,9 +443,11 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
 github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@@ -464,6 +471,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -569,6 +577,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
@@ -656,6 +665,7 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU=
@@ -729,6 +739,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -766,6 +777,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -853,6 +865,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -899,6 +912,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -931,17 +945,27 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
 k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
+k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8=
+k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
 k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
+k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA=
+k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
 k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ=
+k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
+k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
 k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
 k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
+k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
@@ -952,5 +976,11 @@ mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
+sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
+sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
+sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/internal/app/diun.go b/internal/app/diun.go
index 2d8c24b8..03ef439d 100644
--- a/internal/app/diun.go
+++ b/internal/app/diun.go
@@ -12,6 +12,7 @@ import (
 	"github.com/crazy-max/diun/v4/internal/notif"
 	dockerPrd "github.com/crazy-max/diun/v4/internal/provider/docker"
 	filePrd "github.com/crazy-max/diun/v4/internal/provider/file"
+	kubernetesPrd "github.com/crazy-max/diun/v4/internal/provider/kubernetes"
 	swarmPrd "github.com/crazy-max/diun/v4/internal/provider/swarm"
 	"github.com/crazy-max/diun/v4/pkg/registry"
 	"github.com/hako/durafmt"
@@ -123,6 +124,11 @@ func (di *Diun) Run() {
 		di.createJob(job)
 	}
 
+	// Kubernetes provider
+	for _, job := range kubernetesPrd.New(di.cfg.Providers.Kubernetes).ListJob() {
+		di.createJob(job)
+	}
+
 	// File provider
 	for _, job := range filePrd.New(di.cfg.Providers.File).ListJob() {
 		di.createJob(job)
diff --git a/internal/model/provider_kubernetes.go b/internal/model/provider_kubernetes.go
new file mode 100644
index 00000000..8dd1714e
--- /dev/null
+++ b/internal/model/provider_kubernetes.go
@@ -0,0 +1,29 @@
+package model
+
+import (
+	"github.com/crazy-max/diun/v4/pkg/utl"
+)
+
+// PrdKubernetes holds kubernetes provider configuration
+type PrdKubernetes struct {
+	Endpoint         string   `yaml:"endpoint" json:"endpoint,omitempty" validate:"omitempty"`
+	Token            string   `yaml:"token,omitempty" json:"token,omitempty" validate:"omitempty"`
+	TokenFile        string   `yaml:"tokenFile,omitempty" json:"tokenFile,omitempty" validate:"omitempty,file"`
+	CertAuthFilePath string   `yaml:"certAuthFilePath" json:"certAuthFilePath,omitempty" validate:"omitempty"`
+	TLSInsecure      *bool    `yaml:"tlsInsecure" json:"tlsInsecure,omitempty" validate:"required"`
+	Namespaces       []string `yaml:"namespaces" json:"namespaces,omitempty" validate:"omitempty"`
+	WatchByDefault   *bool    `yaml:"watchByDefault" json:"watchByDefault,omitempty" validate:"required"`
+}
+
+// GetDefaults gets the default values
+func (s *PrdKubernetes) GetDefaults() *PrdKubernetes {
+	n := &PrdKubernetes{}
+	n.SetDefaults()
+	return n
+}
+
+// SetDefaults sets the default values
+func (s *PrdKubernetes) SetDefaults() {
+	s.TLSInsecure = utl.NewFalse()
+	s.WatchByDefault = utl.NewFalse()
+}
diff --git a/internal/model/providers.go b/internal/model/providers.go
index 4fb3fd96..d3977406 100644
--- a/internal/model/providers.go
+++ b/internal/model/providers.go
@@ -2,9 +2,10 @@ package model
 
 // Providers represents a provider configuration
 type Providers struct {
-	Docker *PrdDocker `yaml:"docker,omitempty" json:"docker,omitempty" label:"allowEmpty"`
-	Swarm  *PrdSwarm  `yaml:"swarm,omitempty" json:"swarm,omitempty" label:"allowEmpty"`
-	File   *PrdFile   `yaml:"file,omitempty" json:"file,omitempty"`
+	Docker     *PrdDocker     `yaml:"docker,omitempty" json:"docker,omitempty" label:"allowEmpty"`
+	Swarm      *PrdSwarm      `yaml:"swarm,omitempty" json:"swarm,omitempty" label:"allowEmpty"`
+	Kubernetes *PrdKubernetes `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty" label:"allowEmpty"`
+	File       *PrdFile       `yaml:"file,omitempty" json:"file,omitempty"`
 }
 
 // GetDefaults gets the default values
diff --git a/internal/provider/kubernetes/kubernetes.go b/internal/provider/kubernetes/kubernetes.go
new file mode 100644
index 00000000..be2788e7
--- /dev/null
+++ b/internal/provider/kubernetes/kubernetes.go
@@ -0,0 +1,49 @@
+package kubernetes
+
+import (
+	"github.com/crazy-max/diun/v4/internal/model"
+	"github.com/crazy-max/diun/v4/internal/provider"
+	"github.com/rs/zerolog"
+	"github.com/rs/zerolog/log"
+)
+
+// Client represents an active kubernetes provider object
+type Client struct {
+	*provider.Client
+	config *model.PrdKubernetes
+	logger zerolog.Logger
+}
+
+// New creates new kubernetes provider instance
+func New(config *model.PrdKubernetes) *provider.Client {
+	return &provider.Client{
+		Handler: &Client{
+			config: config,
+			logger: log.With().Str("provider", "kubernetes").Logger(),
+		},
+	}
+}
+
+// ListJob returns job list to process
+func (c *Client) ListJob() []model.Job {
+	if c.config == nil {
+		return []model.Job{}
+	}
+
+	images := c.listPodImage()
+	if len(images) == 0 {
+		log.Warn().Msg("No image found")
+		return []model.Job{}
+	}
+
+	c.logger.Info().Msgf("Found %d image(s) to analyze", len(images))
+	var list []model.Job
+	for _, image := range images {
+		list = append(list, model.Job{
+			Provider: "kubernetes",
+			Image:    image,
+		})
+	}
+
+	return list
+}
diff --git a/internal/provider/kubernetes/pod.go b/internal/provider/kubernetes/pod.go
new file mode 100644
index 00000000..93a47dca
--- /dev/null
+++ b/internal/provider/kubernetes/pod.go
@@ -0,0 +1,48 @@
+package kubernetes
+
+import (
+	"reflect"
+
+	"github.com/crazy-max/diun/v4/internal/model"
+	"github.com/crazy-max/diun/v4/internal/provider"
+	"github.com/crazy-max/diun/v4/pkg/k8s"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func (c *Client) listPodImage() []model.Image {
+	cli, err := k8s.New(k8s.Options{
+		Endpoint:         c.config.Endpoint,
+		Token:            c.config.Token,
+		TokenFile:        c.config.TokenFile,
+		CertAuthFilePath: c.config.CertAuthFilePath,
+		TLSInsecure:      c.config.TLSInsecure,
+		Namespaces:       c.config.Namespaces,
+	})
+	if err != nil {
+		c.logger.Error().Err(err).Msg("Cannot create Kubernetes client")
+		return []model.Image{}
+	}
+
+	pods, err := cli.PodList(metav1.ListOptions{})
+	if err != nil {
+		c.logger.Error().Err(err).Msg("Cannot list Kubernetes pods")
+		return []model.Image{}
+	}
+
+	var list []model.Image
+	for _, pod := range pods {
+		for _, ctn := range pod.Spec.Containers {
+			image, err := provider.ValidateContainerImage(ctn.Image, pod.Annotations, *c.config.WatchByDefault)
+			if err != nil {
+				c.logger.Error().Err(err).Msgf("Cannot get image from container %s (pod %s)", ctn.Name, pod.Name)
+				continue
+			} else if reflect.DeepEqual(image, model.Image{}) {
+				c.logger.Debug().Msgf("Watch disabled for container %s (pod %s)", ctn.Name, pod.Name)
+				continue
+			}
+			list = append(list, image)
+		}
+	}
+
+	return list
+}
diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go
new file mode 100644
index 00000000..d70c9062
--- /dev/null
+++ b/pkg/k8s/client.go
@@ -0,0 +1,121 @@
+package k8s
+
+import (
+	"context"
+	"io/ioutil"
+	"os"
+
+	"github.com/crazy-max/diun/v4/pkg/utl"
+	"github.com/pkg/errors"
+	"github.com/rs/zerolog/log"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/clientcmd"
+)
+
+// Client represents an active kubernetes object
+type Client struct {
+	ctx        context.Context
+	namespaces []string
+	API        *kubernetes.Clientset
+}
+
+// Options holds kubernetes client object options
+type Options struct {
+	Endpoint         string
+	Token            string
+	TokenFile        string
+	CertAuthFilePath string
+	TLSInsecure      *bool
+	Namespaces       []string
+}
+
+// New initializes a new Kubernetes client
+func New(opts Options) (*Client, error) {
+	var err error
+	var api *kubernetes.Clientset
+
+	switch {
+	case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "":
+		log.Debug().Msgf("Creating in-cluster Kubernetes provider client %s", opts.Endpoint)
+		api, err = newInClusterClient(opts)
+	case os.Getenv("KUBECONFIG") != "":
+		log.Debug().Msgf("Creating cluster-external Kubernetes provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG"))
+		api, err = newExternalClusterClientFromFile(opts, os.Getenv("KUBECONFIG"))
+	default:
+		log.Debug().Msgf("Creating cluster-external Kubernetes provider client %s", opts.Endpoint)
+		api, err = newExternalClusterClient(opts)
+	}
+
+	if len(opts.Namespaces) == 0 {
+		opts.Namespaces = []string{metav1.NamespaceAll}
+	}
+
+	return &Client{
+		ctx:        context.Background(),
+		namespaces: opts.Namespaces,
+		API:        api,
+	}, err
+}
+
+func newInClusterClient(opts Options) (*kubernetes.Clientset, error) {
+	config, err := rest.InClusterConfig()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create in-cluster configuration")
+	}
+
+	if opts.Endpoint != "" {
+		config.Host = opts.Endpoint
+	}
+	if opts.TLSInsecure != nil {
+		config.TLSClientConfig.Insecure = *opts.TLSInsecure
+	}
+
+	return kubernetes.NewForConfig(config)
+}
+
+func newExternalClusterClientFromFile(opts Options, file string) (*kubernetes.Clientset, error) {
+	configFromFlags, err := clientcmd.BuildConfigFromFlags("", file)
+	if err != nil {
+		return nil, err
+	}
+	if opts.TLSInsecure != nil {
+		configFromFlags.TLSClientConfig.Insecure = *opts.TLSInsecure
+	}
+
+	return kubernetes.NewForConfig(configFromFlags)
+}
+
+func newExternalClusterClient(opts Options) (*kubernetes.Clientset, error) {
+	var err error
+
+	if opts.Endpoint == "" {
+		return nil, errors.New("Endpoint missing for external cluster client")
+	}
+
+	opts.Token, err = utl.GetSecret(opts.Token, opts.TokenFile)
+	if err != nil {
+		return nil, errors.Wrap(err, "Cannot retrieve bearer token")
+	}
+
+	config := &rest.Config{
+		Host:        opts.Endpoint,
+		BearerToken: opts.Token,
+	}
+
+	if opts.CertAuthFilePath != "" {
+		caData, err := ioutil.ReadFile(opts.CertAuthFilePath)
+		if err != nil {
+			return nil, errors.Wrap(err, "Failed to read CA file")
+		}
+		config.TLSClientConfig = rest.TLSClientConfig{
+			CAData: caData,
+		}
+	}
+	if opts.TLSInsecure != nil {
+		config.TLSClientConfig.Insecure = *opts.TLSInsecure
+	}
+
+	return kubernetes.NewForConfig(config)
+}
diff --git a/pkg/k8s/pod.go b/pkg/k8s/pod.go
new file mode 100644
index 00000000..15b08a7e
--- /dev/null
+++ b/pkg/k8s/pod.go
@@ -0,0 +1,27 @@
+package k8s
+
+import (
+	"sort"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// PodList returns Kubernetes pods
+func (c *Client) PodList(opts metav1.ListOptions) ([]v1.Pod, error) {
+	var podList []v1.Pod
+
+	for _, ns := range c.namespaces {
+		pods, err := c.API.CoreV1().Pods(ns).List(c.ctx, opts)
+		if err != nil {
+			return nil, err
+		}
+		podList = append(podList, pods.Items...)
+	}
+
+	sort.Slice(podList, func(i, j int) bool {
+		return podList[i].Name < podList[j].Name
+	})
+
+	return podList, nil
+}


Test patch:
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 807b92bf..941b5ef8 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -125,6 +125,10 @@ func TestLoadFile(t *testing.T) {
 						TLSVerify:      utl.NewTrue(),
 						WatchByDefault: utl.NewFalse(),
 					},
+					Kubernetes: &model.PrdKubernetes{
+						TLSInsecure:    utl.NewFalse(),
+						WatchByDefault: utl.NewTrue(),
+					},
 					File: &model.PrdFile{
 						Filename: "./fixtures/dummy.yml",
 					},
diff --git a/internal/config/fixtures/config.test.yml b/internal/config/fixtures/config.test.yml
index 79d51008..88c0d0d0 100644
--- a/internal/config/fixtures/config.test.yml
+++ b/internal/config/fixtures/config.test.yml
@@ -67,5 +67,7 @@ providers:
     watchByDefault: true
     watchStopped: true
   swarm: {}
+  kubernetes:
+    watchByDefault: true
   file:
     filename: ./fixtures/dummy.yml