<?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: Yevhen Tienkaiev</title>
    <description>The latest articles on DEV Community by Yevhen Tienkaiev (@hronom).</description>
    <link>https://dev.to/hronom</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%2F347508%2F44364b76-580e-4e7b-91ab-6bd67a967c92.png</url>
      <title>DEV Community: Yevhen Tienkaiev</title>
      <link>https://dev.to/hronom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hronom"/>
    <language>en</language>
    <item>
      <title>How to install Cursor in Ubuntu</title>
      <dc:creator>Yevhen Tienkaiev</dc:creator>
      <pubDate>Sun, 06 Apr 2025 17:12:59 +0000</pubDate>
      <link>https://dev.to/hronom/how-to-install-cursor-in-ubuntu-4nb5</link>
      <guid>https://dev.to/hronom/how-to-install-cursor-in-ubuntu-4nb5</guid>
      <description>&lt;h1&gt;
  
  
  Step 1 - download
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.cursor.com/downloads" rel="noopener noreferrer"&gt;https://www.cursor.com/downloads&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2 - install
&lt;/h1&gt;

&lt;p&gt;Install dependencies:&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="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;libfuse2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a directory for the application to install into:&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="nb"&gt;sudo mkdir&lt;/span&gt; /opt/cursor/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move to downloaded app to folder:&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="nb"&gt;sudo mv &lt;/span&gt;Cursor-0.48.7-x86_64.AppImage /opt/cursor/cursor.AppImage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make app executable:&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="nb"&gt;sudo chmod&lt;/span&gt; +x /opt/cursor/cursor.AppImage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download image for icon:&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="nb"&gt;sudo &lt;/span&gt;wget https://raw.githubusercontent.com/getcursor/docs/refs/heads/main/images/logo/app-logo.svg &lt;span class="nt"&gt;-O&lt;/span&gt; /opt/cursor/app-logo.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 3 - create a link to app
&lt;/h1&gt;

&lt;p&gt;Create a desktop entry:&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="nb"&gt;sudo &lt;/span&gt;nano /usr/share/applications/cursor.desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Desktop Entry]
Name=Cursor
Exec=/opt/cursor/cursor.AppImage --no-sandbox
Icon=/opt/cursor/app-logo.svg
Type=Application
Categories=Development;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>cursor</category>
      <category>ubuntu</category>
      <category>programming</category>
    </item>
    <item>
      <title>Grafana Cloud PDC agent and Istio</title>
      <dc:creator>Yevhen Tienkaiev</dc:creator>
      <pubDate>Sun, 13 Aug 2023 17:18:18 +0000</pubDate>
      <link>https://dev.to/hronom/grafana-pdc-agent-and-istio-ea0</link>
      <guid>https://dev.to/hronom/grafana-pdc-agent-and-istio-ea0</guid>
      <description>&lt;p&gt;When you need to setup &lt;strong&gt;G&lt;/strong&gt;rafana &lt;strong&gt;P&lt;/strong&gt;rivate &lt;strong&gt;D&lt;/strong&gt;ata source &lt;strong&gt;C&lt;/strong&gt;onnect in Kubernetes you need to apply some tricks in order to make it work.&lt;/p&gt;

&lt;p&gt;Here I will describe what I did in order to use it.&lt;/p&gt;

&lt;p&gt;Used links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://grafana.com/docs/grafana-cloud/connect-externally-hosted/configure-private-datasource-connect/#set-up-a-private-data-source-connection" rel="noopener noreferrer"&gt;Official guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created my custom helm chart that contains next &lt;code&gt;deployment.yaml&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;apps/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;Deployment&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;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&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;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.minReplicas&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;RollingUpdate&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;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&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;proxy.istio.io/config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;holdApplicationUntilProxyStarts: true&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&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;CLUSTER&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&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;HOSTED_GRAFANA_ID&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hostedGrafanaId&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&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;TOKEN&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;token&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-cluster&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$(CLUSTER)"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-gcloud-hosted-grafana-id&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$(HOSTED_GRAFANA_ID)"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-token&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$(TOKEN)"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-ssh-key-file&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/pdc/.ssh/grafana_pdc_v3"&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;grafana/pdc-agent:{{ .Values.version }}&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1024m&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Gi&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1024m&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Gi&lt;/span&gt;
          &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30000&lt;/span&gt;
        &lt;span class="na"&gt;runAsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30000&lt;/span&gt;
        &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30000&lt;/span&gt;
      &lt;span class="na"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;maxSkew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
          &lt;span class="na"&gt;minDomains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.minReplicas&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes.io/hostname"&lt;/span&gt;
          &lt;span class="na"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoNotSchedule&lt;/span&gt;
          &lt;span class="na"&gt;matchLabelKeys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pod-template-hash&lt;/span&gt;
          &lt;span class="na"&gt;nodeAffinityPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Honor&lt;/span&gt;
          &lt;span class="na"&gt;nodeTaintsPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Honor&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Release.Name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;maxSkew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
          &lt;span class="na"&gt;minDomains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.minReplicas&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;topologyKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topology.kubernetes.io/zone"&lt;/span&gt;
          &lt;span class="na"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoNotSchedule&lt;/span&gt;
          &lt;span class="na"&gt;matchLabelKeys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pod-template-hash&lt;/span&gt;
          &lt;span class="na"&gt;nodeAffinityPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Honor&lt;/span&gt;
          &lt;span class="na"&gt;nodeTaintsPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Honor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Nuances
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Istio
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Sidecar
&lt;/h4&gt;

&lt;p&gt;Set &lt;code&gt;holdApplicationUntilProxyStarts: true&lt;/code&gt; for the pods, so they will not start until istio sidecar not starts.&lt;/p&gt;

&lt;h4&gt;
  
  
  Access (optional)
&lt;/h4&gt;

&lt;p&gt;If you not allow outbound traffic - set &lt;code&gt;ServiceEntry&lt;/code&gt; that &lt;a href="https://grafana.com/docs/grafana-cloud/connect-externally-hosted/configure-private-datasource-connect/#before-you-begin" rel="noopener noreferrer"&gt;will allow several urls&lt;/a&gt;.&lt;br&gt;
What I have for &lt;code&gt;API&lt;/code&gt; access:&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;networking.istio.io/v1alpha3&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;ServiceEntry&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;-api&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;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;private-datasource-connect-api-&amp;lt;cluster&amp;gt;.grafana.net&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MESH_EXTERNAL&lt;/span&gt;
  &lt;span class="na"&gt;ports&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;https&lt;/span&gt;
      &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPS&lt;/span&gt;
  &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DNS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I have for &lt;code&gt;ssh&lt;/code&gt; access:&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;networking.istio.io/v1alpha3&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;ServiceEntry&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;-ssh&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;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;private-datasource-connect-&amp;lt;cluster&amp;gt;.grafana.net&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MESH_EXTERNAL&lt;/span&gt;
  &lt;span class="na"&gt;ports&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;tcp&lt;/span&gt;
      &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DNS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grafana PDC config
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Key Pair force regeneration
&lt;/h4&gt;

&lt;p&gt;I set &lt;code&gt;-ssh-key-file&lt;/code&gt; to &lt;code&gt;/home/pdc/.ssh/grafana_pdc_v3&lt;/code&gt; because if there already host in allowed list(for ssh access) - then it not starts and fail in constant restarts.&lt;br&gt;
This should be addressed in &lt;a href="https://github.com/grafana/pdc-agent/issues/21" rel="noopener noreferrer"&gt;GitHub issue&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Log level
&lt;/h4&gt;

&lt;p&gt;Currently, in PDC agent log level set to &lt;code&gt;debug&lt;/code&gt; level.&lt;br&gt;
Unfortunately, as of today, when you use &lt;code&gt;-ssh-key-file&lt;/code&gt; parameter you cannot change it.&lt;br&gt;
This should be addressed in &lt;a href="https://github.com/grafana/pdc-agent/issues/20" rel="noopener noreferrer"&gt;GitHub issue&lt;/a&gt;&lt;/p&gt;

</description>
      <category>istio</category>
      <category>pdc</category>
      <category>kubernetes</category>
      <category>grafanacloud</category>
    </item>
    <item>
      <title>Configure Grafana Cloud SAML to work with JumpCloud</title>
      <dc:creator>Yevhen Tienkaiev</dc:creator>
      <pubDate>Tue, 08 Aug 2023 22:54:44 +0000</pubDate>
      <link>https://dev.to/hronom/configure-grafana-cloud-saml-to-work-with-jumpcloud-2l92</link>
      <guid>https://dev.to/hronom/configure-grafana-cloud-saml-to-work-with-jumpcloud-2l92</guid>
      <description>&lt;h2&gt;
  
  
  JumpCloud SAML
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Display Label: Grafana Cloud

IdP Entity ID: JumpCloud
SP Entity ID: https://bla.grafana.net/saml/metadata
ACS URL: https://bla.grafana.net/saml/acs
SAMLSubject NameID: email
SAMLSubject NameID Format: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
Signature Algorithm: RSA-SHA256
Sign Assertion: &amp;lt; checked &amp;gt;
Default Relay State: https://bla.grafana.net/
Login URL: https://bla.grafana.net/login
Declare Redirect Endpoint:  &amp;lt; checked &amp;gt;
IDP URL: https://sso.jumpcloud.com/saml2/bla1

User Attributes:
Service Provider Attribute Name: displayName ; JumpCloud Attribute Name: fullname
Service Provider Attribute Name: mail ; JumpCloud Attribute Name: email
Service Provider Attribute Name: username ; JumpCloud Attribute Name: username

GROUP ATTRIBUTES:
Include group attribute: group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generate certificate
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/saml/#certificate-and-private-key" rel="noopener noreferrer"&gt;official guide&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana Cloud SAML
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;General settings
Display name for this SAML 2.0 integration: JumpCloud
Allow signup: &amp;lt; checked &amp;gt;
Auto login: &amp;lt; checked &amp;gt;
Single logout: &amp;lt; unchecked &amp;gt;
Identity provider initiated login: &amp;lt; checked &amp;gt;
Relay state *: https://bla.grafana.net/
Max issue delay: 90s
Metadata valid duration: 48h

Key and certificate
Signing and encryption key and certificate (required): Base64-encoded content
Private key: &amp;lt; upload key.pem file from step Generate certificate&amp;gt;
Certificate: &amp;lt; upload cert.pem file from step Generate certificate &amp;gt;
Sign requests: &amp;lt; checked &amp;gt;
Signature algorithm: RSA-SHA256 (default)

Connect Grafana with Identity Provider
IdP's metadata: URL for metadata ; Copy Metadata URL from JumpCloud

User mapping
Name attribute: displayName
Login attribute: username
Email attribute: mail
Groups attribute: &amp;lt; blank &amp;gt;
Role attribute: group
Org attribute: &amp;lt; blank &amp;gt;

Role mapping
Editor: developers
Admin: admins
Skip organization role sync: &amp;lt; unchecked &amp;gt;
Allowed organizations: &amp;lt; blank &amp;gt;
Name identifier format: Email address

Test and enable
Hit button "Save and Enable"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Nuances
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Make sure that &lt;code&gt;displayName&lt;/code&gt; has text as Grafana SAML not accept empty value. This means that in JumpCloud you should have &lt;code&gt;fullname&lt;/code&gt; set&lt;/li&gt;
&lt;li&gt;Example on how added multiple roles:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;role_values_admin = DevOps,Admins
role_values_editor = Build,"Extra Engineering"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IDP URL&lt;/code&gt; should be unique for all applications on your JumpCloud account&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Setup OpenCost in Kubernetes using Helm</title>
      <dc:creator>Yevhen Tienkaiev</dc:creator>
      <pubDate>Mon, 31 Jul 2023 23:36:46 +0000</pubDate>
      <link>https://dev.to/hronom/setup-opencost-in-kubernetes-using-helm-8mf</link>
      <guid>https://dev.to/hronom/setup-opencost-in-kubernetes-using-helm-8mf</guid>
      <description>&lt;p&gt;My setup is AWS and EKS.&lt;/p&gt;

&lt;p&gt;My work based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud-native.slack.com/archives/C03D56FPD4G/p1690566466507759?thread_ts=1690557341.236339&amp;amp;cid=C03D56FPD4G" rel="noopener noreferrer"&gt;Slack conversation here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.opencost.io/docs/configuration/on-prem#custom-pricing-using-the-opencost-helm-chart" rel="noopener noreferrer"&gt;Custom pricing using the OpenCost Helm Chart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Original from &lt;a href="https://github.com/opencost/opencost/blob/develop/configs/aws.json" rel="noopener noreferrer"&gt;source&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created custom helm chart based on &lt;a href="https://artifacthub.io/packages/helm/opencost/opencost" rel="noopener noreferrer"&gt;OpenCost helm chart(official?)&lt;/a&gt; here is my default &lt;code&gt;values.yaml&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;fullnameOverride&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opencost&lt;/span&gt;

&lt;span class="na"&gt;awsSpotDataRegion&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="na"&gt;awsSpotDataBucket&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="na"&gt;awsAccountId&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="na"&gt;opencost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceAccount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xxx"&lt;/span&gt;
  &lt;span class="na"&gt;opencost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;exporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;defaultClusterId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xxx"&lt;/span&gt;
      &lt;span class="na"&gt;extraEnv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;EMIT_KSM_V1_METRICS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
        &lt;span class="na"&gt;EMIT_KSM_V1_METRICS_ONLY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
        &lt;span class="na"&gt;PROM_CLUSTER_ID_LABEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster&lt;/span&gt;
        &lt;span class="na"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt; &lt;span class="c1"&gt;#error&lt;/span&gt;
      &lt;span class="na"&gt;extraVolumeMounts&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;opencost-conf&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/models/aws.json&lt;/span&gt;
          &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws.json&lt;/span&gt;
    &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://mimir-nginx.mimir.svc.cluster.local:80/prometheus"&lt;/span&gt;
      &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;extraVolumes&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;opencost-conf&lt;/span&gt;
      &lt;span class="na"&gt;configMap&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;opencost-conf&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws.json"&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also in my custom chart I included &lt;code&gt;configmap.yaml&lt;/code&gt;, here is content of it:&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;ConfigMap&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;opencost-conf&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;aws.json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"provider": "custom",&lt;/span&gt;
        &lt;span class="s"&gt;"description": "Default prices used to compute allocation between RAM and CPU. AWS pricing API data still used for total node cost.",&lt;/span&gt;
        &lt;span class="s"&gt;"CPU": "0.031611",&lt;/span&gt;
        &lt;span class="s"&gt;"spotCPU": "0.006655",&lt;/span&gt;
        &lt;span class="s"&gt;"RAM": "0.004237",&lt;/span&gt;
        &lt;span class="s"&gt;"GPU": "0.95",&lt;/span&gt;
        &lt;span class="s"&gt;"spotRAM": "0.000892",&lt;/span&gt;
        &lt;span class="s"&gt;"storage": "0.00005479452",&lt;/span&gt;
        &lt;span class="s"&gt;"zoneNetworkEgress": "0.01",&lt;/span&gt;
        &lt;span class="s"&gt;"regionNetworkEgress": "0.01",&lt;/span&gt;
        &lt;span class="s"&gt;"internetNetworkEgress": "0.143",&lt;/span&gt;
        &lt;span class="s"&gt;"spotLabel": "",&lt;/span&gt;
        &lt;span class="s"&gt;"spotLabelValue": "",&lt;/span&gt;
        &lt;span class="s"&gt;"awsServiceKeyName": "",&lt;/span&gt;
        &lt;span class="s"&gt;"awsServiceKeySecret": "",&lt;/span&gt;
        &lt;span class="s"&gt;"awsSpotDataRegion": "{{ .Values.awsSpotDataRegion }}",&lt;/span&gt;
        &lt;span class="s"&gt;"awsSpotDataBucket": "{{ .Values.awsSpotDataBucket }}",&lt;/span&gt;
        &lt;span class="s"&gt;"awsSpotDataPrefix": "",&lt;/span&gt;
        &lt;span class="s"&gt;"athenaBucketName": "s3://x",&lt;/span&gt;
        &lt;span class="s"&gt;"athenaRegion": "us-east-1",&lt;/span&gt;
        &lt;span class="s"&gt;"athenaDatabase": "",&lt;/span&gt;
        &lt;span class="s"&gt;"athenaTable": "",&lt;/span&gt;
        &lt;span class="s"&gt;"projectID": "{{ .Values.awsAccountId }}"&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>kubernetes</category>
      <category>opencost</category>
      <category>helm</category>
      <category>aws</category>
    </item>
    <item>
      <title>Spring Boot + Quartz Scheduler in Cluster mode</title>
      <dc:creator>Yevhen Tienkaiev</dc:creator>
      <pubDate>Sat, 03 Dec 2022 19:01:03 +0000</pubDate>
      <link>https://dev.to/hronom/spring-boot-quartz-scheduler-in-cluster-mode-35ej</link>
      <guid>https://dev.to/hronom/spring-boot-quartz-scheduler-in-cluster-mode-35ej</guid>
      <description>&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%2F46kcbh5k7b0j2qjwcxbs.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%2F46kcbh5k7b0j2qjwcxbs.png" alt="Quartz Scheduler in Cluster mode" width="400" height="225"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;I want to share my experience in setupping and configuring Quartz Scheduler in Cluster mode with Spring Boot.&lt;/p&gt;




&lt;h2&gt;
  
  
  Modules description
&lt;/h2&gt;

&lt;p&gt;So we have two application in the project: supervisor and worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supervisor&lt;/strong&gt;  -  is the main application there you can manage jobs(schedule/remove/check statuses). Besides, I didn't find how to disable execution of jobs on master, so it also have an a "worker" capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker&lt;/strong&gt;  -  simply executes scheduled jobs, nothing interesting.&lt;/p&gt;

&lt;p&gt;I use MySQL as database for Quartz cluster. For simplicity I launch it in docker by using docker compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The config files…
&lt;/h2&gt;

&lt;p&gt;Spring config file for &lt;strong&gt;supervisor&lt;/strong&gt; contains connection string to the database and credentials and port that will be used for REST API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;endpoints:
    jmx:
        unique-names: true
server:
    port: 8080
spring:
    datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        password: 12345
        url: jdbc:mysql://localhost/test?createDatabaseIfNotExist=true&amp;amp;useSSL=false&amp;amp;allowPublicKeyRetrieval=true
        username: root
    liquibase:
        change-log: classpath:db/changelog/db.changelog-master.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/Hronom/spring-boot-quartz-cluster-example/blob/master/supervisor/src/main/resources/application.yml" rel="noopener noreferrer"&gt;application.yml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also it contains path to the file with rules for initial creation of tables in MySQL. The SQL tables creation scripts are from &lt;a href="http://www.quartz-scheduler.org/downloads/" rel="noopener noreferrer"&gt;quartz-2.2.3-distribution.tar.gz/quartz-2.2.3-distribution.tar&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spring config file for &lt;strong&gt;worker&lt;/strong&gt; are almost same — it not contains info for initial table creation and different REST API port.&lt;/p&gt;

&lt;p&gt;Quartz config files for &lt;strong&gt;supervisor&lt;/strong&gt; and &lt;strong&gt;worker&lt;/strong&gt; are also almost identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName=spring-boot-quartz-cluster-example
org.quartz.scheduler.instanceId=AUTO

#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.threadCount=1

#============================================================================
# Configure JobStore
#============================================================================
#org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# In new spring use LocalDataSourceJobStore https://github.com/spring-projects/spring-framework/issues/27709
org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.tablePrefix=QRTZ_

org.quartz.jobStore.isClustered=true
org.quartz.jobStore.clusterCheckinInterval=20000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the difference is only in how many thread pool are — &lt;code&gt;org.quartz.threadPool.threadCount&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The code…
&lt;/h2&gt;

&lt;p&gt;So for first we need autowiring support for jobs, and spring still not have support for this: &lt;a href="https://jira.spring.io/browse/SPR-14471" rel="noopener noreferrer"&gt;SPR-14471&lt;/a&gt;, &lt;a href="https://jira.spring.io/browse/SPR-9698" rel="noopener noreferrer"&gt;SPR-9698&lt;/a&gt;. &lt;em&gt;Really, vote for this issues!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fortunately the workaround is &lt;a href="https://gist.github.com/jelies/5085593#file-autowiringspringbeanjobfactory-java" rel="noopener noreferrer"&gt;available&lt;/a&gt; and I say many thanks to this guy and still don’t understand why it’s not in spring yet…&lt;/p&gt;

&lt;h3&gt;
  
  
  TestJob1
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.github.hronom.spring.boot.quartz.cluster.example.common.job;

import com.github.hronom.spring.boot.quartz.cluster.example.common.service.TestService;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

@DisallowConcurrentExecution
public class TestJob1 implements Job {

    @Autowired
    private TestService testService;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        try {
            String id = jobExecutionContext.getJobDetail().getKey().getName();
            testService.run(id);
        } catch (Exception e) {
            throw new JobExecutionException(e);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It just call method of the autowired local class that currently runs inside instance where job executed. Because service can throw exception, I add capturing of all exception and wrap it in &lt;code&gt;JobExecutionException&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  TestServiceImpl
&lt;/h3&gt;

&lt;p&gt;The service are also simple - it emulates long running process and randomly throws exceptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.github.hronom.spring.boot.quartz.cluster.example.supervisor.services;

import com.github.hronom.spring.boot.quartz.cluster.example.common.service.TestService;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {
    private final Log logger = LogFactory.getLog(getClass());

    private final Random random = new Random();

    public void run(String id) throws Exception {
        logger.info("Running job on supervisor, job id " + id);
        if (random.nextInt(3) == 1) {
            throw new Exception("Randomly generated test exception on supervisor");
        }
        try {
            Thread.sleep(TimeUnit.MINUTES.toMillis(1));
        } catch (InterruptedException e) {
            logger.error("Error", e);
        }
        logger.info("Completed job on supervisor, job id " + id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throwing of exception added to check mechanic how Quartz handle exceptions in jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  JobsListenerService
&lt;/h3&gt;

&lt;p&gt;Each instance of &lt;strong&gt;supervisor&lt;/strong&gt;/&lt;strong&gt;worker&lt;/strong&gt; has a jobs listener to listen events raised by local execution of jobs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.github.hronom.spring.boot.quartz.cluster.example.common.service;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.springframework.stereotype.Service;

@Service
public class JobsListenerService implements JobListener {
    private final Log logger = LogFactory.getLog(getClass());

    @Override
    public String getName() {
        return "Main Listener";
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        logger.info("Job to be executed " + context.getJobDetail().getKey().getName());
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        logger.info("Job execution vetoed " + context.getJobDetail().getKey().getName());
    }

    @Override
    public void jobWasExecuted(
        JobExecutionContext context, JobExecutionException jobException
    ) {
        logger.info(
            "Job was executed " +
            context.getJobDetail().getKey().getName() +
            (jobException != null ? ", with error" : "")
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SchedulerConfig
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.github.hronom.spring.boot.quartz.cluster.example.supervisor.configs;

import com.github.hronom.spring.boot.quartz.cluster.example.common.spring.AutowiringSpringBeanJobFactory;
import com.github.hronom.spring.boot.quartz.cluster.example.common.service.JobsListenerService;

import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import java.io.IOException;
import java.util.Properties;

import javax.sql.DataSource;

import liquibase.integration.spring.SpringLiquibase;

@Configuration
@EnableAsync
@EnableScheduling
public class SchedulerConfig {

    @Bean
    public JobFactory jobFactory(
        ApplicationContext applicationContext,
        // Injecting SpringLiquibase to ensure liquibase is already initialized and created the Quartz tables
        SpringLiquibase springLiquibase
    ) {
        AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        return jobFactory;
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory, JobsListenerService jobsListenerService)
        throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);
        factory.setJobFactory(jobFactory);
        factory.setQuartzProperties(quartzProperties());
        factory.setGlobalJobListeners(jobsListenerService);
        // https://medium.com/@rudra.ramesh/use-following-code-in-supervisor-app-while-creating-schedulerfactorybean-object-now-supervisor-fd2f95365350
        // If you need to disable launching of jobs on supervisor use this:
        //factory.setAutoStartup(false);
        return factory;
    }

    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see we create custom &lt;code&gt;JobFactory&lt;/code&gt; — &lt;code&gt;AutowiringSpringBeanJobFactory&lt;/code&gt; for autowiring support in jobs.&lt;/p&gt;

&lt;p&gt;Also we create &lt;code&gt;SchedulerFactoryBean&lt;/code&gt; that sets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data source(MySQL)&lt;/li&gt;
&lt;li&gt;custom job factory for autowiring&lt;/li&gt;
&lt;li&gt;Quartz properties from config file&lt;/li&gt;
&lt;li&gt;jobs global listener(listener this listen for events of the local scheduler)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JobsService
&lt;/h3&gt;

&lt;p&gt;This service contains methods to manage jobs on the &lt;strong&gt;supervisor&lt;/strong&gt;, &lt;strong&gt;worker&lt;/strong&gt; doesn’t has such service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.github.hronom.spring.boot.quartz.cluster.example.supervisor.services;

import com.github.hronom.spring.boot.quartz.cluster.example.common.job.TestJob1;
import com.github.hronom.spring.boot.quartz.cluster.example.supervisor.controllers.JobStatus;

import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.utils.Key;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

@Service
public class JobsService {
    private final String groupName = "normal-group";

    private final Scheduler scheduler;

    @Autowired
    public JobsService(SchedulerFactoryBean schedulerFactory) {
        this.scheduler = schedulerFactory.getScheduler();
    }

    public List&amp;lt;String&amp;gt; addNewJobs(int jobs) throws SchedulerException {
        LinkedList&amp;lt;String&amp;gt; list = new LinkedList&amp;lt;&amp;gt;();
        for (int i = 0; i &amp;lt; jobs; i++) {
            list.add(addNewJob());
        }
        return list.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
    }

    public String addNewJob() throws SchedulerException {
        String id = UUID.randomUUID().toString();

        JobDetail job =
            newJob(TestJob1.class)
                .withIdentity(id, groupName)
                // http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/ConfigJDBCJobStoreClustering.html
                // https://stackoverflow.com/a/19270566/285571
                .requestRecovery(true)
                .build();

        Trigger trigger =
            newTrigger()
                .withIdentity(id + "-trigger", groupName)
                .startNow()
                .withSchedule(
                    simpleSchedule().withIntervalInSeconds(30)
                )
                .build();

        scheduler.scheduleJob(job, trigger);

        return id;
    }

    public boolean deleteJob(String id) throws SchedulerException {
        JobKey jobKey = new JobKey(id, groupName);
        return scheduler.deleteJob(jobKey);
    }

    public List&amp;lt;String&amp;gt; getJobs() throws SchedulerException {
        return scheduler
            .getJobKeys(GroupMatcher.jobGroupEquals(groupName))
            .stream()
            .map(Key::getName)
            .sorted(Comparator.naturalOrder())
            .collect(Collectors.toList());
    }

    /**
     * Check realization was inspired by https://stackoverflow.com/a/31479434/285571
     */
    public List&amp;lt;JobStatus&amp;gt; getJobsStatuses() throws SchedulerException {
        LinkedList&amp;lt;JobStatus&amp;gt; list = new LinkedList&amp;lt;&amp;gt;();
        for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            List&amp;lt;? extends Trigger&amp;gt; triggers = scheduler.getTriggersOfJob(jobDetail.getKey());
            for (Trigger trigger : triggers) {
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                if (Trigger.TriggerState.COMPLETE.equals(triggerState)) {
                    list.add(new JobStatus(jobKey.getName(), true));
                } else {
                    list.add(new JobStatus(jobKey.getName(), false));
                }
            }
        }
        list.sort(Comparator.comparing(o -&amp;gt; o.id));
        return list;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For test purposes we use method &lt;code&gt;addNewJobs&lt;/code&gt; to add new jobs in batch mode(by default we add 10 jobs)&lt;/p&gt;

&lt;p&gt;The method &lt;code&gt;addNewJob&lt;/code&gt; adds new jobs to scheduler by doing next things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create job detail there we acquire job id and group name. Also we set that job must be &lt;a href="http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/ConfigJDBCJobStoreClustering.html" rel="noopener noreferrer"&gt;recoverable&lt;/a&gt;. The recoverability of job means that if one node in cluster fails/crashes while processing job, the job will be processed on other alive node. More description about this &lt;a href="http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/ConfigJDBCJobStoreClustering.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://stackoverflow.com/a/19270566/285571" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Creating trigger with interval 30 sec. We want to fire trigger only once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also we have other interesting method &lt;code&gt;getJobsStatuses&lt;/code&gt;. It helps check status of jobs across all cluster. For doing that we retrieve all available jobs details and check the state of trigger for this job details. So if trigger state is &lt;code&gt;COMPLETE&lt;/code&gt; this means that trigger has already fired and job now executing. &lt;/p&gt;




&lt;p&gt;This realization was inspired by: &lt;a href="https://stackoverflow.com/a/31479434/285571" rel="noopener noreferrer"&gt;https://stackoverflow.com/a/31479434/285571&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s all, full source code available at GitHub: &lt;a href="https://github.com/Hronom/spring-boot-quartz-cluster-example" rel="noopener noreferrer"&gt;https://github.com/Hronom/spring-boot-quartz-cluster-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also I want to say thanks to post that gives me a good starting point: &lt;a href="http://www.opencodez.com/java/quartz-scheduler-with-spring-boot.htm" rel="noopener noreferrer"&gt;http://www.opencodez.com/java/quartz-scheduler-with-spring-boot.htm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>java</category>
      <category>springboot</category>
      <category>quartz</category>
    </item>
  </channel>
</rss>
