<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gokhan Sari</title>
    <description>The latest articles on DEV Community by Gokhan Sari (@th0th).</description>
    <link>https://dev.to/th0th</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F128991%2F1ce54179-cbb6-439e-b0a4-e2ea5d3fa36e.jpg</url>
      <title>DEV Community: Gokhan Sari</title>
      <link>https://dev.to/th0th</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/th0th"/>
    <language>en</language>
    <item>
      <title>Kubernetes service accounts, and creating kubeconfig for one</title>
      <dc:creator>Gokhan Sari</dc:creator>
      <pubDate>Sat, 09 Nov 2024 10:59:55 +0000</pubDate>
      <link>https://dev.to/th0th/kubernetes-service-accounts-and-creating-kubeconfig-for-one-2kma</link>
      <guid>https://dev.to/th0th/kubernetes-service-accounts-and-creating-kubeconfig-for-one-2kma</guid>
      <description>&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/security/service-accounts/" rel="noopener noreferrer"&gt;Official kubernetes docs&lt;/a&gt; say&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A service account is a type of non-human account that, in Kubernetes, provides a distinct identity in a Kubernetes cluster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Service accounts can be used for providing access both to components inside the cluster and other services from outside the cluster. For example, I use a service account to give access to GitHub actions to deploy new version of the &lt;a href="https://www.webgazer.io" rel="noopener noreferrer"&gt;WebGazer&lt;/a&gt; on a new release.&lt;/p&gt;

&lt;h2&gt;
  
  
  "But I already have a kubeconfig that I use with kubectl, managing the cluster"
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faabw8bpebt0ppf5jd40n.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faabw8bpebt0ppf5jd40n.jpg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please don't copy/paste your &lt;code&gt;cluster-admin&lt;/code&gt; role kubeconfig 🙃 If you do, that allows other party to do whatever they want to do to every resource, on every namespace, cluster-wide. Even if the tool or service you create the service account is very trustworthy, and not malicious; there might be a bug in their systems somehow accidentally editing or deleting unexpected resources.&lt;/p&gt;

&lt;p&gt;That's why you should create a service account specific for that tool or service, and with limited permissions that is enough for what you expect the tool or service to do (see &lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" rel="noopener noreferrer"&gt;Principle of least privilege&lt;/a&gt;). You know, better safe than sorry.&lt;/p&gt;

&lt;h2&gt;
  
  
  A service account, a role and a role binding
&lt;/h2&gt;

&lt;p&gt;For the GitHub actions deploying WebGazer on the cluster case, my configuration is similar to this:&lt;/p&gt;

&lt;h3&gt;
  
  
  ServiceAccount and its token secret
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer-github&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;serviceaccount-webgazer-github.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/service-account.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer-github&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer-github-token&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/service-account-token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;secret-webgazer-github.yaml&lt;/p&gt;

&lt;p&gt;Before version 1.24, Kubernetes created secret for the service account automatically. But after 1.24, we manually need to create a secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfi1gjymwca7k2ubynhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfi1gjymwca7k2ubynhb.png" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From kubernetes docs&lt;/p&gt;

&lt;h3&gt;
  
  
  Role and binding
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;rules&lt;/code&gt; in the &lt;code&gt;Role&lt;/code&gt; resource is the important. That is where you allow the service account to do certain stuff on the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer-github&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;apps&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;networking.k8s.io&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rbac.authorization.k8s.io&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;role-webgazer-github.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer-github&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer-github&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webgazer-github&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RoleBinding&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating kubeconfig for service account
&lt;/h2&gt;

&lt;p&gt;There are some shell scripts you can find on the internet. But the most convenient method I found is &lt;a href="https://github.com/superbrothers" rel="noopener noreferrer"&gt;Kazuki Suda&lt;/a&gt;'s &lt;a href="https://github.com/superbrothers/kubectl-view-serviceaccount-kubeconfig-plugin" rel="noopener noreferrer"&gt;view-serviceaccount-kubeconfig&lt;/a&gt; kubectl plugin.&lt;/p&gt;

&lt;p&gt;You will need &lt;a href="https://github.com/kubernetes-sigs/krew" rel="noopener noreferrer"&gt;krew&lt;/a&gt; to install that plugin, which is the most popular kubectl package I know. I used homebew to install krew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;krew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see other methods you can use to install krew, head over to &lt;a href="https://krew.sigs.k8s.io/docs/user-guide/setup/install/" rel="noopener noreferrer"&gt;krew's installation docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have krew installed, you can install view-serviceaccount-kubeconfig plugin, too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl krew &lt;span class="nb"&gt;install &lt;/span&gt;view-serviceaccount-kubeconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then you can use the plugin to create the kubeconfig:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl view-serviceaccount-kubeconfig &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; default &lt;span class="se"&gt;\&lt;/span&gt;
  webgazer-github
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it will output the kubeconfig's content. You can use that kubeconfig wherever you need to access the cluster with the service account you created.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>kubeconfig</category>
    </item>
    <item>
      <title>Automatic PostgreSQL backups to AWS S3 (Kubernetes CronJob)</title>
      <dc:creator>Gokhan Sari</dc:creator>
      <pubDate>Mon, 01 Jul 2024 16:31:00 +0000</pubDate>
      <link>https://dev.to/th0th/automatic-postgresql-backups-to-aws-s3-kubernetes-cronjob-268h</link>
      <guid>https://dev.to/th0th/automatic-postgresql-backups-to-aws-s3-kubernetes-cronjob-268h</guid>
      <description>&lt;p&gt;I am not going to spend too much time explaining how important database backups are, I assume we are past that point since you are already here, reading this. But let's start with the saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your data is as good as your last backup, and your backup is as good as your ability to restore it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here at WebGazer, we work hard to squeeze out the most valuable reports from the data collected. In addition to downtime notifications, we try to provide actionable insights that will help our customers improve their infrastructures proactively. That's why we pay great attention to integrity of our database, but every once in a while something goes south and a data migration causes some data loss, or an unforeseen case for an SQL query breaks the data. I am not going to say those times are easy. I still get anxious doing something manual on the production database, but backups, at least, ease the pain.&lt;/p&gt;

&lt;p&gt;Anyway, let's get to the real stuff. In this tutorial, we are going to back up our PostgreSQL database, and upload the backup to AWS S3 every morning at 07:00. We are going to need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A PostgreSQL DBMS running on a Kubernetes cluster (duh!)&lt;/li&gt;
&lt;li&gt;An AWS S3 bucket (or something compatible)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kubernetes CronJob
&lt;/h3&gt;

&lt;p&gt;"cron" is originally a command line job scheduling utility developed in 1975 by AT&amp;amp;T Bell Laboratories. But ever since, &lt;strong&gt;"cronjob"&lt;/strong&gt; is the common name for scheduled, periodic jobs in computer science terminology. Kubernetes has a workload type called &lt;code&gt;CronJob&lt;/code&gt; that serves the very same purpose, and &lt;a href="https://kubernetes.io/blog/2021/04/08/kubernetes-1-21-release-announcement/#cronjobs-graduate-to-stable" rel="noopener noreferrer"&gt;it is generally available since v1.21 (April 2021)&lt;/a&gt;. That is what we are going to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  postgres-s3-backup docker image
&lt;/h3&gt;

&lt;p&gt;I created a docker image that uses &lt;a href="https://www.postgresql.org/docs/current/app-pgdump.html" rel="noopener noreferrer"&gt;&lt;code&gt;pg_dump&lt;/code&gt;&lt;/a&gt; to create a backup for a PostgreSQL database, and uploads it to S3 using &lt;a href="https://rclone.org" rel="noopener noreferrer"&gt;&lt;code&gt;rclone&lt;/code&gt;&lt;/a&gt;. The source code for this image is available on &lt;a href="https://github.com/th0th/postgres-s3-backup" rel="noopener noreferrer"&gt;https://github.com/th0th/postgres-s3-backup&lt;/a&gt;, and the docker image is available on Docker Hub, &lt;a href="https://hub.docker.com/r/th0th/postgres-s3-backup" rel="noopener noreferrer"&gt;&lt;code&gt;th0th/postgres-s3-backup&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This image uses environment variables for configuration, you can see all configuration options on &lt;a href="https://github.com/th0th/postgres-s3-backup?tab=readme-ov-file#environment-variables" rel="noopener noreferrer"&gt;README&lt;/a&gt;. Here is an example Kubernetes CronJob resource definition that uses this image (you need to replace the placeholders indicated between &lt;code&gt;&amp;lt; &amp;gt;&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CronJob&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-backup&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;NAMESPACE&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jobTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;AWS_ACCESS_KEY_ID&amp;gt;&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS_REGION&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;AWS_REGION&amp;gt;&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS_S3_ENDPOINT&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;AWS_S3_ENDPOINT&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# this is the path in the bucket (e.g. backups/postgres)&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;AWS_SECRET_ACCESS_KEY&amp;gt;&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;POSTGRES_DB&amp;gt;&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_HOST&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;POSTGRES_HOST&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# this is hostname for the PostgreSQL service. if not set explicitly, defaults to "postgres"&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;POSTGRES_PASSWORD&amp;gt;&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PORT&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;POSTGRES_PORT&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# if not set explicitly, defaults to "5432"&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;POSTGRES_USER&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# if not set explicitly, defaults to "postgres"&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_VERSION&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;POSTGRES_VERSION&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# version of the PostgreSQL server. "14", "15" or "16". Defaults to "16".&lt;/span&gt;
              &lt;span class="c1"&gt;# - name: WEBGAZER_HEARTBEAT_URL&lt;/span&gt;
              &lt;span class="c1"&gt;#   value: &amp;lt;WEBGAZER_HEARTBEAT_MONITOR_URL&amp;gt; # we will talk about this in the BONUS section below&lt;/span&gt;
              &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;th0th/postgres-s3-backup:0.2&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-backup&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;7&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt; &lt;span class="c1"&gt;# this runs on 07:00 every day. see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#schedule-syntax&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BONUS: Monitoring with WebGazer Heartbeat Monitoring
&lt;/h3&gt;

&lt;p&gt;WebGazer offers &lt;a href="https://dev.to/docs/heartbeat-monitors/introduction"&gt;heartbeat monitoring&lt;/a&gt; for making sure cron jobs like this run as they are supposed to. It basically works like this: you set a heartbeat monitor on WebGazer, send programmatic HTTP requests from the cron job to that monitor's URL. If the monitor doesn't get the request in time, it alerts you. So you get to know if a database backup isn't completed at the time it should be completed.&lt;/p&gt;

&lt;p&gt;The docker image I mentioned has this built-in, just uncomment the lines near the end, and set the &lt;code&gt;WEBGAZER_HEARTBEAT_URL&lt;/code&gt; to the heartbeat monitor's URL you get from WebGazer, and you are good to go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WEBGAZER_HEARTBEAT_URL&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://heartbeat.webgazer.io/1-GjluPbM8GIu15xkC9mQDRtfxJBmAGa7P&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, when &lt;code&gt;WEBGAZER_HEARTBEAT_URL&lt;/code&gt; is set, this docker image reports a heartbeat parameter called &lt;code&gt;seconds&lt;/code&gt;, total seconds it takes to take the database backup and upload it to S3, you can add a &lt;a href="https://dev.to/docs/heartbeat-monitors/settings#rules"&gt;rule&lt;/a&gt; to the heartbeat monitor, too.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>backup</category>
      <category>kubernetes</category>
      <category>aws</category>
    </item>
    <item>
      <title>I built the free and open source, privacy-first Google Analytics alternative</title>
      <dc:creator>Gokhan Sari</dc:creator>
      <pubDate>Mon, 29 May 2023 13:59:50 +0000</pubDate>
      <link>https://dev.to/th0th/i-built-the-free-and-open-source-privacy-first-google-analytics-alternative-2oco</link>
      <guid>https://dev.to/th0th/i-built-the-free-and-open-source-privacy-first-google-analytics-alternative-2oco</guid>
      <description>&lt;p&gt;Hey folks!&lt;/p&gt;

&lt;p&gt;For about the last two years, I have been working on this. I have recently published it, so I want to share with you all, and ask for your feedback, too!&lt;/p&gt;

&lt;p&gt;Some highlights you might find interesting are:&lt;/p&gt;

&lt;p&gt;✊ It is 100% free and open source (AGPL).&lt;br&gt;
✌️ It doesn't collect any personal data, so it is out-of-the-box compliant with data privacy regulations like GDPR, CCPA, PECR and KVKK.&lt;br&gt;
🎯 It is simple analytics. It provides consolidated real-time reports on a single-page.&lt;br&gt;
🪶 It has a very lightweight (&amp;lt; 1KB) script. It doesn't cause any harm on the website's performance.&lt;/p&gt;

&lt;p&gt;You can see a demo of the reports page &lt;a href="https://www.poeticmetric.com/s?d=www.poeticmetric.com" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Even though you can see the stack yourself on the repository, here are some of the stuff I used building it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt; for backend,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; (TypeScript, Bootstrap) for frontend,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; for the single source of truth database,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ClickHouse&lt;/strong&gt; for time series analytics data,&lt;/li&gt;
&lt;li&gt;and &lt;strong&gt;RabbitMQ&lt;/strong&gt; as the task queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can give it a go on &lt;a href="https://www.poeticmetric.com?utm_source=dev.to&amp;amp;utm_term=showdev"&gt;poeticmetric.com&lt;/a&gt;, and the check its full source code on &lt;a href="https://github.com/th0th/poeticmetric" rel="noopener noreferrer"&gt;https://github.com/th0th/poeticmetric&lt;/a&gt;. I also published ready-to-use Docker images for self-hosting (&lt;a href="https://www.poeticmetric.com/docs/open-source/self-hosting" rel="noopener noreferrer"&gt;guide here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I would love to what you think of it!&lt;/p&gt;

&lt;p&gt;Cheers,&lt;/p&gt;

</description>
      <category>go</category>
      <category>nextjs</category>
      <category>react</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
