diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..828edde0 --- /dev/null +++ b/404.html @@ -0,0 +1,1264 @@ + + + +
+ + + + + + + + + + + + + + +Apache Airflow er et verktøy for å orkestrere, skedulere og monitorere datapipelines. +Web-grensesnittet til Airflow gir brukeren enkel tilgang til å lese logger fra de ulike stegene i pipelinen, trigge datapipelines manuelt og sjekke statistikk på tidligere kjøringer.
+En datapipeline i Airflow, eller DAG (Directed Acyclic Graph), er et sett med oppgaver man ønsker å kjøre som beskriver rekkefølge og avhengigheter mellom oppgavene. +Disse DAG-ene beskrives programmatisk i python filer og legges i et Github repo som periodisk synkroniseres med Airflow instansen. +Nedenfor ser du en en grafisk representasjon av flyten i en DAG:
+flowchart LR
+ A(email_start) --> C(waiting_1)
+ B(slack_start) --> C(waiting_1)
+ C --> D(fetch_styrk)
+ C --> E(fetch_nace)
+ C --> F(fetch_pam)
+ D --> G(waiting_2)
+ E --> G(waiting_2)
+ F --> G(waiting_2)
+ G --> H(transform_styrk)
+ G --> I(transform_nace)
+ G --> J(transform_pam)
+ H --> K(waiting_3)
+ I --> K(waiting_3)
+ J --> K(waiting_3)
+ K --> L(slack_success)
+ K --> M(email_success)
+NADA tilbyr team eller enkeltpersoner å sette opp Airflow instanser i KNADA gjennom Knorten.
+For mer informasjon om Airflow, se Airflow docs
+Vær oppmerksom på at alt av logger fra en Airflow task vil skrives til en bucket i knada-gcp
prosjektet, og være tilgjengelig etterpå for Airflow og direkte for NADA som er admins i knada-gcp
. Vær derfor forsiktig så ikke sensitiv informasjon skrives til stdout i koden som kjøres.
For å bruke Airflow i KNADA kreves det et Git-repo under navikt
organisasjonen på Github.com som inneholder Python-filer med DAGer.
Hvert minutt vil Github repoet bli synkronisert til Airflow instansen, og Airflow vil lese DAGene definert i repoet.
+Vi har laget et bibliotek med flere enkle operators for å lette jobben når man lager DAGer. +Dette ligger ute på PyPi.org og er dokumentert der.
+Foreløpig har vi fire operators, hvor alle støtter å klone et annet repo ved oppstart av en task, og installerer eksterne Python-avhengigheter via en requirements.txt
fil i ditt repo.
I KNADA er Airflow konfigurert til å bruke Kubernetes Executor. +Dette innebærer at hver task i en Airflow DAG vil spinne opp og kjøre en egen worker i en separat Kubernetes pod. +Det gjør at man står fritt til å selv spesifisere miljøet til Airflow-workeren.
+Nedenfor har vi listet opp noen av de konfigurasjonen vi tror er nyttig å vite om.
+Merk: Hovedcontaineren som worker-poden bruker vil alltid hete base
, så dersom en ønsker å overskrive noe som gjelder spesifikt for denne containeren må man referere til den med navn som i eksemplene under.
Dersom en ikke spesifiserer ressursbehov for Airflow taskene sine vil de kjøre med standard instillinger som er 512 MB
minne, 0.5
vCPU, og 1Gi
disk (ephemeral-storage
).
+Dette kan man enkelt overstyre for alle operators gitt at de tar utgangspunkt i BaseOperator til Airflow.
+Under følger et eksempel på hvordan ressursbehov for en task endres til 2GB
minne,2
vCPU, og 5Gi
disk:
from airflow import DAG
+from airflow.utils.dates import days_ago
+from airflow.operators.python_operator import PythonOperator
+from kubernetes import client as k8s
+
+def myfunc():
+ print("kjør task")
+
+with DAG('min-dag', start_date=days_ago(1), schedule_interval=None) as dag:
+ run_this = PythonOperator(
+ task_id='test',
+ python_callable=myfunc,
+ executor_config={
+ "pod_override": k8s.V1Pod(
+ spec=k8s.V1PodSpec(
+ containers=[
+ k8s.V1Container(
+ name="base",
+ resources={
+ "requests": {
+ "cpu": "2",
+ "memory": "2Gi",
+ "ephemeral-storage": "5Gi"
+ }
+ }
+ )
+ ]
+ )
+ )
+ },
+ dag=dag
+ )
+
For skallsikring av Airflow må man i hver task spesifisere hvilke eksterne tjenester (les: tjenester utenfor Airflow) man skal få lov til å snakke med. +Vi har stengt av muligheten for eksterne tjenester å snakke inn til Airflow.
+I podene hvor Airflow tasken kjører blokkeres i utgangspunktet all trafikk ut, med følgende unntak:
+Utover dette er man nødt til å eksplisitt spesifisere hvilke kilder man trenger å snakke med (gjelder både interne og eksterne addresser) for hver enkelt task i en DAG.
+Dette gjør man ved å legge på en allowlist
annotasjon på pod ressursen med hostnavnet og porten på det man trenger å nå.
from airflow import DAG
+from airflow.utils.dates import days_ago
+from airflow.operators.python_operator import PythonOperator
+from airflow.contrib.operators.slack_webhook_operator import SlackWebhookOperator
+from kubernetes import client as k8s
+import os
+import logging
+
+def myfunc():
+ import requests
+ res = requests.get("https://ssb.no/api")
+ res.raise_for_status()
+
+with DAG('min-dag', start_date=days_ago(1), schedule_interval=None) as dag:
+ slack = SlackWebhookOperator(
+ http_conn_id=None,
+ task_id="slack-message",
+ webhook_token=os.environ["SLACK_TOKEN"],
+ message="start min-dag",
+ channel="#minkanal",
+ link_names=True,
+ executor_config={
+ "pod_override": k8s.V1Pod(
+ metadata=k8s.V1ObjectMeta(annotations={"allowlist": "hooks.slack.com"})
+ )
+ }
+ )
+
+ run_this = PythonOperator(
+ task_id='test',
+ python_callable=myfunc,
+ executor_config={
+ "pod_override": k8s.V1Pod(
+ metadata=k8s.V1ObjectMeta(annotations={"allowlist": "ssb.no,db.adeo.no:1521"})
+ )
+ },
+ dag=dag)
+
+ slack >> run_this
+
allowlist
er en kommaseparert liste med hostnavn og port på formatet hostnavn:port
.
+Dersom man ikke angir port vil vi bruke 443
som standardport.
+Når jobben er ferdig vil tilgangene bli fjernet.
Man kan også sette allowlist
for operators som lages med dataverk-airflow som i eksempelet under.
from airflow import DAG
+from airflow.utils.dates import days_ago
+from dataverk_airflow import notebook_operator
+
+with DAG('dag', start_date=days_ago(1), schedule_interval=None) as dag:
+ task = notebook_operator(dag=dag,
+ name="knada-pod-operator",
+ repo="navikt/repo",
+ nb_path="notebooks/mynb.ipynb",
+ allowlist=["ssb.no", "db.adeo.no:1521"])
+
Hvis du kun har behov for andre Python-biblioteker så anbefaler vi på det sterkeste at du bruker Dataverk Airflow og sender med en requirements.txt
fil i stedet for å bygge ditt eget image.
I noen tilfeller har du kanskje flere avhengigheter enn det vi tilbyr i standard Airflow-oppsett. +Da kan det å bygge sitt eget Docker image være en løsning. +Du kan se hva vi tilbyr i våre images, og hvordan disse er bygget i navikt/knada-images. +Våre Docker imager kommer med drivere for Oracle og Postgres, men inneholder ikke et stort utvalg av Python biblioteker.
+Se her for å spesifisere eget image som brukes av standard Airflow workere. +Se her for å spesifisere eget image som brukes av dataverk-airflow workere.
+Har du behov for at hele Airflow instansen skal bruke ditt Docker image så spesifiseres det i Knorten.
+NB: Hvis du bygger image lokalt på en nyere Mac så er det viktig at du bygger imaget for riktig plattform.
+Legg til --platform linux/amd64
i docker build
kommandoen.
Vi tillater ikke at Airflow worker containere kjører med root privilegier. Dersom du bygger ditt eget image må dette imaget ha en bruker airflow
med uid 50000
.
Skal man bygge eget image som skal overstyre standard worker imaget er man nødt til å ha apache-airflow installert.
+Dette vil man få dersom man tar utgangspunkt i enten vårt base image eller det offisielle airflow imaget.
+I tillegg vil også nødvendig airflow
bruker med uid 50000
være opprettet i miljøet.
+Dersom man ikke tar utgangspunkt i et av disse imagene må man selv installere Apache Airflow i imaget samt opprette airflow
-brukeren.
Under følger et eksempel på hvordan å overstyre imaget som Airflow worker containeren bruker med imaget du selv har bygget:
+from airflow import DAG
+from airflow.utils.dates import days_ago
+from airflow.operators.python_operator import PythonOperator
+from kubernetes import client as k8s
+
+def myfunc():
+ print("kjør task")
+
+with DAG('min-dag', start_date=days_ago(1), schedule_interval=None) as dag:
+ run_this = PythonOperator(
+ task_id='test',
+ python_callable=myfunc,
+ executor_config={
+ "pod_override": k8s.V1Pod(
+ spec=k8s.V1PodSpec(
+ containers=[
+ k8s.V1Container(
+ name="base",
+ image="ghcr.io/navikt/mitt-airflow-image:v1"
+ )
+ ]
+ )
+ )
+ },
+ dag=dag
+ )
+
Skal man bygge eget image for å overstyre dataverk-airflow
sitt standard image anbefaler vi å ta utgangspunkt i et av våre dataverk-airflow imager for den ønskede python versjonen.
+Dette imaget vil bygge på det offisielle python imaget samt inneholde drivere for oracle, postgres, i tillegg til quarto.
Dersom du bygger eget image og ønsker å bruke quarto_operator
fra dataverk-airflow
så har dette biblioteket en avhengighet til kommandolinjeverktøyet knatch og må derfor også installeres i ditt image.
Under følger et eksempel på hvordan å overstyre imaget som dataverk-airflow
containeren bruker med imaget du selv har bygget:
from airflow import DAG
+from airflow.utils.dates import days_ago
+from dataverk_airflow import notebook_operator
+
+with DAG('dag', start_date=days_ago(1), schedule_interval=None) as dag:
+ task = notebook_operator(dag=dag,
+ name="knada-pod-operator",
+ repo="navikt/repo",
+ nb_path="notebooks/mynb.ipynb",
+ image="ghcr.io/navikt/mitt-airflow-image:v1")
+
Har man behov for at en ekstern tjeneste skal snakke med API-et til Airflow trenger Nada å konfigurere noe på "baksiden" og lage en service bruker for dere. +Ta kontakt i Slack#nada, så fikser vi dette for dere. +Vi vil ta opprette en service account for deres Airflow, og lage en ekstern adresse (som tilgangsstyres med Cloud Armor).
+Et typisk scenario for dette er å la IWS styre jobber i Airflow. +Akkurat dette scenarioet er også dokumentert i Confluence/Analytisk Plattform.
+Som et risikoreduserendetiltak logger vi hvem som kjører hvilke jobber i KNADA ned til Datavarehus. +dette er for at Datavarehus skal ha bedre kontroll på hvem som snakker med de. +Selve tjenesten heter knaudit, og det er kun Datavarehus og NADA som har tilgang til disse loggene.
+Eksempel på hva vi logger:
+{"commit_sha1":["d19dcf695f043c6eff6b0cc2478b58d45299ca97"],"hostname":["mycsvdag-starting-fc8dfe28afae414da33a5d2a57db85d1"],"run_id":["scheduled__2023-05-03T05:30:00+00:00"],"timestamp":["2023-05-03T05:35:11.000Z"],"git_repo":["github.com/navikt/test-team-dag"],"ip":["321.312.312.321"],"namespace":["team-test-ncnv"],"task_id":["starting"],"git_branch":["main"],"dag_id":["MyCSVDAG"],"triggered_by":["airflow"]}
+
Hvis det er en manuell kjøring så vil triggered_by
være satt til NAV-ident for den innlogget Airflow-bruker.
++Cloud Composer is a managed Apache Airflow service that helps you create, schedule, monitor and manage workflows. +Cloud Composer automation helps you create Airflow environments quickly and use Airflow-native tools, such as the powerful Airflow web interface and command line tools, so you can focus on your workflows and not your infrastructure.
+
Du finner den offisielle dokumentasjonen for Cloud Composer hos cloud.google.com.
+For å sette opp en ny Cloud Composer instans gjennom Google Cloud Platform konsollen (GCP),
+gå til Cloud Composer, trykk CREATE
og velg Composer 2
Følgende må spesifiseres
+Name
- navn på Cloud Composer instansenLocation
- regionen hvor instansen settes opp (må velge Europa)CREATE
Når man setter opp Cloud Composer så opprettes det automatisk en bucket som er knyttet til Composer instansen. Før neste steg er det viktig å notere seg navnet på bucketen som blir opprettet. Det gjøres ved å gå til Cloud Storage i konsollen og finne bucketen som har goog-composer-environment
labels.
I bucketen som opprettes når man setter opp Cloud Composer i Opprett ny composer instans blir det automatisk laget en katalog som heter dags
.
+Denne katalogen inneholder beskrivelsene av datapipelinene (DAGs) som man kan orkestrere med Cloud Composer og dette synkroniseres kontinuerlig med instansen.
For å ha revisjonskontroll på disse pipelinebeskrivelsene lønner det seg å sette opp en synkronisering mot et Github repo.
+CREATE SERVICE ACCOUNT
Service account name
og en Service account description
Storage Legacy Bucket Writer
Storage Object Admin
Merk deg eposten som den nyopprettede service accounten får.
+KEYS
ADD KEY
-> Create new key
-> JSON
Name
satt til GCP_CREDENTIALS og Value
til innholdet i JSON-nøkkelen lastet ned i Last ned service account nøkkeldags
i repoetname: Sync DAG-bucket
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - '.github/workflows/sync-gcs-bucket.yaml'
+ - 'dags/**'
+
+jobs:
+ sync-gcs-bucket:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: google-github-actions/auth@v2
+ with:
+ credentials_json: "${{ secrets.GCP_CREDENTIALS }}"
+ - name: "Set up Cloud SDK"
+ uses: google-github-actions/setup-gcloud@v2
+ - name: "Specify GCP project"
+ run: "gcloud config set project PROJECT"
+ - name: "Sync DAGs to GCS bucket"
+ run: gsutil cp -r dags gs://BUCKET
+
Erstatt BUCKET i workflowen over med navnet på bucketen som ble opprettet i Opprett ny composer instans og PROJECT med gcp prosjektet bucketen ligger i (finnes her)
+Ved push til main branch vil denne workflowen laste opp innholdet i dags
katalogen i repoet til GCS bucketen
+som igjen blir synkronisert med Cloud Composer instansen.
Vi krever at eksplisitt spesifiserer hvilke eksterne tjenester man skal kommunisere med per tjeneste man bruker i Knada. +Åpninger er basert på IP-adresser eller DNS (nettadresser), samt porten for den hosten de ønsker å åpne mot.
+Du angir hostene du har lyst til å allowliste på formatet <ip-adresse>
:<port>
.
+Dersom port utelates vil vi bruke 443
som standardport.
Eksempler:
+For å konfigurere allowlist for Jupyterhub se Trafikk fra notebooks. +For å konfigurere allowlist for Airflow se Trafikk ut fra Airflow.
+*.googleapis.com
(for Secret manager, Storage buckets, BigQuery etc.)github.com
(for lesing av repo med kode)europe-north1-python.pkg.dev
(pypi proxy for installasjon av pakker)*.googleapis.com
(for Secret manager, Storage buckets, BigQuery etc.)github.com
(for lesing av repo med kode)Dersom man bruker dataverk-airflow vil det avhengig av hvilken operator og opsjoner man bruker også bli lagt på nødvendige åpninger. Se repo for dokumentasjon på hva som settes for ulike operatorer.
+private.googleapis.com
(for Secret manager, Storage buckets, BigQuery etc.)Komplett oversikt finner man i navikt/knada-gcp.
+For å få tilgang til en CloudSQL Postgres database på GCP kreves det åpning mot den offentlige IP adressen til databaseinstansen.
+Den offentlige adressen for en database finner du i Cloud console.
+Finn database instansen din i listen, og finn adressen under Public IP address
.
For CloudSQL Postgres databaser er det nødvendig å åpne både port 443
og 3307
.
+Du må dermed legge til to separate innslag for dette:
<ip>:3307
<ip>:443
I Kubernetes bruker vi Network Policies for å styre trafikken inn og ut til pods (les: apper).
+Ved å spesifisere annotasjonen allowlist
på en pod så vil tjenesten Knep lage NetworkPolicy
basert på listen av host:port
.
Datafortellinger brukes til å dele innsikt i form av statiske dokumenter. +Datafortellinger kan enkelt deles med andre i NAV gjennom Datamarkedsplassen.
+Følge installasjonsoppskriften på quarto.org/docs. +Husk å hold Quarto oppdatert.
+Vi anbefaler å følge guiden Tarball Installation On Linux. +Igjen må man selv huske å holde Quarto oppdatert.
+Vi anbefaler å bruke kommandoen nedenfor i din Dockerfile
for å installere Quarto.
+Denne vil hente ned siste versjon av Quarto hver gang Docker-imaget blir bygd.
# jq, wget, og tar må være installert for at kommandoen nedenfor skal fungere
+RUN cd /app && \
+ QUARTO_VERSION=$(curl https://api.github.com/repos/quarto-dev/quarto-cli/releases/latest | jq '.tag_name' | sed -e 's/[\"v]//g') && \
+ wget https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-amd64.tar.gz && \
+ tar -zxvf quarto-${QUARTO_VERSION}-linux-amd64.tar.gz && \
+ mv /app/quarto-${QUARTO_VERSION} /app/quarto
+ENV PATH="${PATH}:/app/quarto/bin"
+
For å programmatisk lage eller oppdatere datafortellinger trenger man å autentisere seg med et team token. For å finne team token for teamene du er medlem av kan du gå til Mine team tokens i Datamarkedsplassen (krever innlogging). Det er forskjellig tokens for dev og prod, så dersom du ønsker å lage eller oppdatere en datafortelling i dev finner du tokens i dev versjonen av Datamarkedsplassen. Samme token brukes både for intern og ekstern publisering av datafortellinger.
+Dersom du har behov for å rotere team tokenet til et av teamene dine (f.eks. dersom du har eksponert tokenet ved et uhell) kan dette gjøres fra Mine team tokens i Datamarkedsplassen.
+Man kan publisere datafortellinger enten internt for NAV ansatte eller eksternt.
+For datafortellinger som kun skal være tilgjengelig for ansatte i NAV kan kan man publisere denne til den interne datamarkedsplassen.
+I eksemplene som følger må følgende byttes ut med reelle verdier:
+${ENV}
knada
VMer og jupyter notebooks/airflow i knada-clusteret
settes dette til datamarkedsplassen.intern.dev.nav.no for dev og datamarkedsplassen.intern.nav.no for prod${STORY_ID}
- erstatt med ID på datafortellingen${TEAM_TOKEN}
- erstatt med team-token fra Datamarkedsplassen${TEAM_ID}
- erstatt med team ID for teamet ditt i teamkatalogenSe Get Started på Quarto sine sider for å komme i gang med utvikling av datafortellingen.
+Når man skal registrere en quarto datafortelling i Datamarkedsplassen kan man enten gjøre dette gjennom brukergrensesnittet eller programmatisk.
+Legg til ny datafortelling
Lagre
Dersom du kun ønsker å registere en tom datafortelling som siden skal oppdateres programmatisk kan man droppe steg (5) over
+Du kan også programmatisk registrere en datafortelling.
+Request body parametere:
+name
(obligatrisk): Navn på datafortellingendescription
: Beskrivelse av datafortellingenteamID
: ID i teamkatalogen for teamet som eier datafortellingen. Nødvendig å spesifisere dersom datafortellingen skal sorteres riktig i produktområdevisningen på data.ansatt.nav.noid
: Kan spesifiseres dersom du ønsker å spesifisere ID for datafortellingen selv. Dersom den utelates genereres det en ny.Headers for requesten
+bearer token
(obligatorisk): Team tokenet for teamet som skal eie datafortellingenMerk: IDen for datafortellingen blir returnert når man gjør en POST til /quarto/create
. Denne må så brukes når datafortellingen skal oppdateres etterpå.
$ curl -X POST \
+ -d '{"name": "min datafortelling", "description": "min beskrivelse", "teamID": "<team-id>", "id": "${STORY_ID}"}' \
+ -H "Authorization: Bearer ${TEAM_TOKEN}" \
+ https://${ENV}/quarto/create
+
import requests
+
+res = requests.post(f"https://${ENV}/quarto/create", headers={"Authorization": "bearer ${TEAM_TOKEN}"}, json={
+ "name": "min quarto",
+ "description": "min beskrivelse",
+ "teamID": "<team-id>",
+ "id": "${STORY_ID}"
+})
+
+story_id = res.json()["id"]
+
For å oppdatere en eksisterende Quarto fortelling må man først generere ressursfilene på nytt med quarto render <file>
.
Deretter må man hente ut ID for Quartoen man ønsker å oppdatere og team-tokenet fra Datamarkedsplassen.
+Eksemplene tar utgangspunkt i at det er filen index.html
som skal lastes opp og at man kjører kommandoene fra samme mappe som filen ligger.
curl -X PUT -F index.html=@index.html \
+ "https://${ENV}/quarto/update/${QUARTO_ID}" \
+ -H "Authorization:Bearer ${TEAM_TOKEN}"
+
#!/bin/bash
+set -e
+
+FILES=""
+for file in <mappe med filene>/*
+do
+ FILES+=" -F $file=@$file"
+done
+
+curl -X PUT $FILES "https://${ENV}/quarto/update/${QUARTO_ID}" \
+ -H "Authorization:Bearer ${TEAM_TOKEN}"
+
import os
+import requests
+
+# A list of file paths to be uploaded
+files_to_upload = [
+ "PATH/index.html"
+ "PATH/SUB/FOLDER/some.html"
+]
+
+multipart_form_data = {}
+for file_path in files_to_upload:
+ file_name = os.path.basename(file_path)
+ with open(file_path, 'rb') as file:
+ # Read the file contents and store them in the dictionary
+ file_contents = file.read()
+ multipart_form_data[file_name] = (file_name, file_contents)
+
+# Send the request with all files in the dictionary
+response = requests.put( f"https://{ENV}/quarto/update/{QUARTO_ID}",
+ headers={"Authorization": f"Bearer {TEAM_TOKEN}"},
+ files=multipart_form_data)
+response.raise_for_status()
+
For å produksjonsette oppdatering av en Quarto Datafortelling med Naisjob er det noe konfigurasjon man må spesifisere i NAIS manifestet og Dockerfilen til jobben.
+quarto render
resulterer i at det genereres noen filer som må lagres midlertidig i containermiljøet før publisering til Datamarkedsplassen. Man er derfor nødt til å legge til annotasjon i Naisjob manifestet for å tillate skriving til filsystemet i containeren
+metadata:
+ annotations:
+ nais.io/read-only-file-system: "false"
+
spec:
+ accessPolicy:
+ outbound:
+ external:
+ - host: data.ekstern.dev.nav.no # for dev
+ - host: data.nav.no # for prod
+
RUN groupadd -g 1069 python && \
+ useradd -r -u 1069 -g python python
+
+USER python
+
Repoet navikt/nada-quarto har et fullstendig eksempel nødvendig oppsett, se spesielt
+I eksempelet hentes team-tokenet fra en kubernetes secret i clusteret og settes som miljøvariabelen NADA_TOKEN
.
Følgende beskriver hvordan man kan publisere datafortellinger eksternt på data.nav.no
.
For eksempler på datafortellinger publisert eksternt, se:
+I eksemplene som følger må følgende byttes ut med reelle verdier:
+${ENV}
${STORY_ID}
- erstatt med ID på datafortellingen${TEAM_TOKEN}
- erstatt med team-token fra DatamarkedsplassenVi støtter foreløpig kun programmatisk registrering av eksterne datafortellinger.
+Request body parametere:
+title
: Navn på datafortellingenslug
: Dersom du selv ønsker å bestemme slug
i URLen til datafortellingenteam
: Navn på teamet som eier datafortellingen. Dette tilsvarer navnet som gjelder for tokenet du henter fra https://data.ansatt.nav.no/user/tokens (eventuelt https://data.intern.dev.nav.no/user/tokens for dev)published
: Dette flagget indikerer om datafortellingen skal listes opp på index siden til data.nav.no
.Dersom en ikke oppgir verken title
eller slug
når datafortellingen registreres så vil det være den genererte UUIDen til datafortellingen som vil brukes for URLen til datafortellingen.
Headers for requesten
+bearer token
(obligatorisk): Team tokenet for teamet som skal eie datafortellingenMerk: IDen for den genererte datafortellingen blir returnert når man gjør en POST til /api/v1/story
. Denne må så brukes når datafortellingen skal oppdateres senere.
$ curl -X POST \
+ -d '{"title": "Min datafortelling om noe", "slug": "min-datafortelling", "team": "<team-navn>"}' \
+ -H "Authorization: Bearer ${TEAM_TOKEN}" \
+ https://${ENV}/api/v1/story
+
import requests
+
+res = requests.post(f"https://${ENV}/api/v1/story", headers={"Authorization": "bearer ${TEAM_TOKEN}"}, json={
+ "title": "Min datafortelling om noe",
+ "slug": "min-datafortelling",
+ "team": "<team-navn>"
+})
+
+story_id = res.json()["id"]
+
curl -X PUT -F index.html=@index.html \
+ "https://${ENV}/api/v1/story/${STORY_ID}" \
+ -H "Authorization:Bearer ${TEAM_TOKEN}"
+
#!/bin/bash
+set -e
+
+FILES=""
+for file in <mappe med filene>/*
+do
+ FILES+=" -F $file=@$file"
+done
+
+curl -X PUT $FILES "https://${ENV}/api/v1/story/${STORY_ID}" \
+ -H "Authorization:Bearer ${TEAM_TOKEN}"
+
import os
+import requests
+
+# A list of file paths to be uploaded
+files_to_upload = [
+ "PATH/index.html"
+ "PATH/SUB/FOLDER/some.html"
+]
+
+multipart_form_data = {}
+for file_path in files_to_upload:
+ file_name = os.path.basename(file_path)
+ with open(file_path, 'rb') as file:
+ # Read the file contents and store them in the dictionary
+ file_contents = file.read()
+ multipart_form_data[file_name] = (file_name, file_contents)
+
+# Send the request with all files in the dictionary
+response = requests.put( f"https://{ENV}/api/v1/story/{STORY_ID}",
+ headers={"Authorization": f"Bearer {TEAM_TOKEN}"},
+ files=multipart_form_data)
+response.raise_for_status()
+
Vi har laget en egen GitHub action - navikt/story-upload - som kan brukes dersom man ønsker å lagre datafortellingen sin i et GitHub repo.
+Denne actionen vil publisere innholdet til en gcs bucket som NADA hoster datafortellinger fra.
+Man kan bruke actionen til å publisere både interne datafortellinger på data.ansatt.nav.no
og eksterne datafortellinger på data.nav.no
.
Se README for github action for beskrivelse av de ulike konfigurerbare input parameterene til actionen
+Under er et eksempel på hvordan å sette opp en enkel github action workflow som oppdaterer en datafortelling internt i dev løsningen til datamarkedsplassen (dvs. data.intern.dev.nav.no
) ved hver push til main
.
+For å bruke actionen må det eksistere en datafortelling som man ønsker å oppdatere. Dersom det ikke finnes en datafortelling så må denne registeres først, se her for å registrere en intern datafortelling, eller her for å registrere en ekstern.
Erstatt ${STORY_ID}
i eksempelet med IDen på datafortellingen du ønsker å oppdatere. Eksempelet tar også utgangspunkt i at team tokenet for teamet som eier datafortellingen er lagt inn som secret på github repoet med nøkkel TEAM_TOKEN
.
+Token for teamet ditt finner du ved å gå til https://data.ansatt.nav.no/user/tokens (eventuelt https://data.intern.dev.nav.no/user/tokens for dev).
name: Eksempel på opplasting av datafortelling
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ oppdater-datafortelling:
+ name: Oppdater datafortelling
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: navikt/story-upload@v1
+ with:
+ id: ${STORY_ID}
+ dir: src
+ team_token: ${{ secrets.TEAM_TOKEN }}
+ env: dev
+ public: "false"
+
Knatch - Knada batch - er et kommandolinjeverktøy tiltenkt å forenkle opplasting av datafortellinger til både den interne datamarkedsplassen og eksterne datafortellinger på data.nav.no
.
Det er særlig nyttig i tilfeller hvor en ønsker å laste opp datafortellinger som består av flere filer da knatch
tar som inputargument en mappe og vil automatisk dele opp filene i mappen og laste de opp i batcher til datamarkedsplassen. Størrelsen på batchene kan du spesifisere med flagget --batch-size
, dersom det utelates vil datafortellingen lastes opp i batcher på 10 filer.
pip install knatch
+
Knatch
kan brukes til å oppdatere en datafortelling både internt i NAV og eksternt.
Begge eksemplene tar utgangspunkt i følgende:
+<id>
i eksemplene under med IDen til den eksisterende datafortellingen.<token>
i eksemplene under med dette tokenet.I eksempelet under publiseres det til dev-løsingen til den interne datamarkedsplassen (datamarkedsplassen.intern.dev.nav.no
). Dersom en i stedet ønsker å publisere til prod så må --host
flagget settes til datamarkedsplassen.intern.nav.no
.
knatch <id> sti/til/mappe/med/filer <token> --host datamarkedsplassen.intern.dev.nav.no
+
I eksempelet under publiseres det til dev-løsingen for ekstern publisering (data.ekstern.dev.nav.no
). Dersom en i stedet ønsker å publisere til prod så må --host
flagget settes til data.nav.no
.
knatch <id> sti/til/mappe/med/filer <token> --host data.ekstern.dev.nav.no --path api/v1/story
+
For å slippe å opprette og bruke builtin users i sql databaser på GCP bør man i stedet bruke Cloud SQL IAM database authentication når man skal koble seg til Cloud SQL databaser for analyse. Da vil man unngå å måtte lese inn passordet for brukeren fra hemmelighet i koden.
+Eksemplene som følger forutsetter:
+At følgende python biblioteker er installert: +
cloud-sql-python-connector
+pandas
+pg8000
+sqlalchemy
+
At den personlige brukeren eller IAM service accounten har rollene Cloud SQL Instance User
og Cloud SQL Client
i GCP prosjektet som eier database instansen du skal koble deg mot. Dette er prosjektnivå roller du gir gjennom IAM i GCP prosjektet.
At den personlige brukeren eller IAM service accounten er lagt til som en Cloud IAM
bruker på database instansen. Se her for hvordan å legge til dette.
Enten manuelt eller med databasemigrasjon må man gi brukeren/service accounten tilgang til å lese fra tabellene i databasen.
+GRANT SELECT ON ALL TABLES IN SCHEMA public to cloudsqliamuser;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO cloudsqliamuser;
+
GRANT SELECT ON ALL TABLES IN SCHEMA public to cloudsqliamserviceaccount;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO cloudsqliamserviceaccount;
+
Hvis du ikke har gjort det i dag, kjør først kommandoen gcloud auth login --update-adc
+
import os
+
+from google.cloud.sql.connector import Connector
+import pg8000
+import pandas as pd
+import sqlalchemy
+
+instance_connection_name = "prosjektID:region:instans" # Erstatt med database instansen du skal koble deg til
+db_iam_user = "epost@nav.no" # Erstatt med din nav epost
+db_name = "navn" # Erstatt med navn på databasen
+
+connector = Connector()
+
+def getconn() -> pg8000.dbapi.Connection:
+ conn: pg8000.dbapi.Connection = connector.connect(
+ instance_connection_name,
+ "pg8000",
+ user=db_iam_user,
+ db=db_name,
+ enable_iam_auth=True,
+ )
+ return conn
+
+engine = sqlalchemy.create_engine(
+ "postgresql+pg8000://",
+ creator=getconn)
+
+query = "SELECT * FROM table"
+df = pd.read_sql_query(query, engine)
+
Dette eksempelet er ment for airflow tasks i KNADA og vil ikke fungere fra notebooks da disse ikke har en binding mot en IAM service account. For notebooks i KNADA, se Lese med personlig bruker.
+import os
+
+from google.cloud.sql.connector import Connector
+import pg8000
+import pandas as pd
+import sqlalchemy
+
+instance_connection_name = "prosjektID:region:instans" # Erstatt med database instansen du skal koble deg til
+db_iam_user = "sa@prosjekt.iam" # Merk: Brukernavnet her skal settes til service account eposten uten .gserviceaccount.com. Altså er service account eposten din mitt-team@knada-gcp.iam.gserviceaccount.com så blir brukernavnet mitt-team@knada-gcp.iam
+db_name = "database" # Erstatt med databasenavn
+
+connector = Connector()
+
+def getconn() -> pg8000.dbapi.Connection:
+ conn: pg8000.dbapi.Connection = connector.connect(
+ instance_connection_name,
+ "pg8000",
+ user=db_iam_user,
+ db=db_name,
+ enable_iam_auth=True,
+ )
+ return conn
+
+engine = sqlalchemy.create_engine(
+ "postgresql+pg8000://",
+ creator=getconn)
+
+query = "SELECT * FROM table"
+df = pd.read_sql_query(query, engine)
+
gcloud auth login --update-adc
install.packages("googleCloudStorageR")
+install.packages("gargle")
+
library(googleCloudStorageR)
+library(gargle)
+
+scope <-c("https://www.googleapis.com/auth/cloud-platform")
+token <- token_fetch(scopes = scope)
+
+gcs_auth(token = token)
+
+gcs_global_bucket("my-bucket")
+obj <- gcs_get_object("file.txt")
+
Google Secret Manager er en tjeneste som gir en sikker og praktisk metode for å lagre API-nøkler, passord, sertifikater og annen sensitiv data.
+Har du hemmeligheter i et eget NAIS team-prosjekt som du ønsker å bruke fra Airflow, så må du gi service accounten for KNADA (denne finner du i Knorten) tilgang til hemmeligheter i ditt team-prosjekt.
+I og med at noen av brukerne i KNADA ikke er knyttet til et NAIS team-prosjekt, tilbyr KNADA Google Secret Manager enten for team eller enkeltpersoner.
+Hvert team som blir opprettet i KNADA igjennom Knorten vil få opprettet sin team-hemmelighet i Google Secret Manager, denne er kun tilgjengelig fra Airflow. +Hver enkelt KNADA-bruker har også muligheten til å bestille sin egen personlige hemmelighet i Google Secret Manager. +Denne er tenkt brukt til å lagre hemmeligheter som kun du skal ha tilgang til.
+Det blir automatisk opprettet en hemmelighet for et team i Knorten, og alle medlemmer av teamet har tilgang. +Personlige hemmeligheter må man selv aktivt opprette, og dette gjør man under Personlige tjenester.
+Det er en lenke fra Knorten til teamets og din personlige secret manager.
+Dokumentasjon for bruk av Google Secret Manager finner man hos Google Cloud.
+En viktig ting å merke seg er at man skiller mellom en secret
og version
.
+For man har kun tilgang til en hemmelighet/secret
igjennom Knorten, men denne kan ha forskjellige version
.
+Vi anbefaler uansett ikke at man har forskjellige hemmeligheter i forskjellige version
, da dette fort kan bli uoversiktlig.
+Det vi anbefaler er å legge alle hemmelighetene deres som en liste i en version
.
+Da kan man lett lese de inn i Python.
For å lese hemmelighter fra Google Secret Manager må du installere Python-biblioteket google-cloud-secret-manager.
+pip install --user google-cloud-secret-manager
+
Koden nedenfor henter alle hemmelighentene fra ditt område og legger de inn i en variabel som heter data
.
import os
+from google.cloud import secretmanager
+
+secrets = secretmanager.SecretManagerServiceClient()
+resource_name = f"{os.environ['KNADA_TEAM_SECRET']}/versions/latest"
+secret = secrets.access_secret_version(name=resource_name)
+data = secret.payload.data.decode('UTF-8')
+
Vi tilbyr private virtuelle maskiner gjennom Knorten, disse kjører i GCP prosjektet knada-gcp
.
+Denne maskinen vil ha tilgang til on-premise kilder på lik linje som Notebooks og Airflow som kjører i KNADA.
Siden dette er en privat maskin stiller vi høyere krav til bruk av maskinene. +Blant annet er du medansvarlig for å holde maskinen oppdatert! +Dette betyr at du må sette deg inn i hvordan man vedlikeholder en Debian GNU/Linux maskin.
+Det samme gjelder for pakker og applikasjoner du installerer. +Du må selv aktivt holde disse oppdatert!
+Hvis du syns dette blir for mye ansvar så anbefaler vi heller Jupyter notebooks.
+Som med Jupyter notebook har man muligheter til å laste ned data til lokal maskin, derfor må man være ekstra obs på at man ikke gjør noe utenfor det behandlingsgrunnlaget man har.
+Man bør være bevist på hva man skriver inn i terminalen (det man skriver ut i terminalen forsvinner så fort man avslutter shellet).
+For eksempel bør man ikke skrive brukernavn og passord direkte i terminalen, men heller bruke konfigurasjons-filer. Enda bedre er det å hente hemmeligheter fra Google Secret Manager.
+Det kan også være fornuftig å konfigurere shellet til å slette historikken når man logger ut.
+Hvis man bruker bash
kan man legge til history -c
i .bash_logout
.
For Debian (selve maskinen/OSet) så kan man lese Keeping your Debian system up-to-date, men i korte trekk handler det om å kjøre aptitude update
etterfulgt av aptitude full-upgrade
.
+Dette vil holde OSet oppdatert, og oppdatere pakker installert via aptitude
(aka apt install
).
For å sikre oss at dette faktisk skjer, så går det en jobb natt til mandag som oppdatere alle maskiner som er skrudd på! +Er du en av de som er flink og skrur av maskinen din når du ikke bruker den, så må du dessverre selv påse at den blir oppdatert.
+For Python bør man regelmessig kjøre pip list --outdated
for å se hva slags pakker man trenger å oppgradere.
+Enda bedre er å ha en requirements.txt
(eller tilsvarende for Poetry eller lignende verktøy) sjekket inn i Github, og la Dependabot gjøre jobben.
+Husk også å holde følge med på nye Python-versjoner!
+Det finnes en god oversikt hos Python developers guide.
+Per dags dato bør ingen være på noe lavere enn 3.8, og man bør jobbe med å komme seg vekk fra 3.8 da den har EOL (end of life) oktober 2024.
For å koble deg til en VM i knada-gcp
trenger du å opprette et SSH nøkkelpar og hente ut noe informasjon om instansen som må fylles inn i SSH-configen lokalt (~/.ssh
).
gcloud auth login
(trengs kun å kjøres en gang om dagen).
+For å gjøre dette må gcloud være installer (se dokumentasjon hos cloud.google.com).gcloud compute ssh --project knada-gcp --zone europe-north1-b <instance>
.
+Erstatt <instance>
med navnet på VM instansen din, denne finner du etter du har logget inn i Knorten under Compute
.
+Denne kommandoen vil også generere SSH-nøkler.gcloud compute ssh --project knada-gcp --zone europe-north1-b <instance> --dry-run
lokalt på maskinen din.
+Erstatt <instance>
med navnet på VM instansen din slik som i punkt (2).Host knada-vm
+ HostName {HOSTNAME}
+ IdentityFile ~/.ssh/google_compute_engine
+ CheckHostIP no
+ HostKeyAlias {HOSTNAME}
+ IdentitiesOnly yes
+ UserKnownHostsFile ~/.ssh/google_compute_known_hosts
+ ProxyCommand {PROXYCOMMAND}
+ ProxyUseFdpass no
+ User {USERNAME}
+
Erstatt {HOSTNAME}, {PROXYCOMMAND} og {USERNAME} med verdiene du får ut av dry-run kommandoen over og lagre filen under ~/.ssh/config
.
+Merk: {USERNAME} skal kun være det før @
i output fra kommandoen over, {HOSTNAME} er det som begynner med compute.
og {PROXYCOMMAND} er alt etter ProxyCommand til og med --verbosity=warning
.
Host knada-vm
+ HostName {HOSTNAME}
+ IdentityFile C:\\Users\\{USERNAME}\\.ssh\\google_compute_engine
+ CheckHostIP no
+ HostKeyAlias {HOSTNAME}
+ IdentitiesOnly yes
+ UserKnownHostsFile C:\\Users\\{USERNAME}\\.ssh\\google_compute_known_hosts
+ ProxyCommand "C:\\Users\\{USERNAME}\\AppData\\Local\\Google\\Cloud SDK\\google-cloud-sdk\\bin\\..\\platform\\bundledpython\\python.exe" "-S" "C:\\Users\\{USERNAME}\\AppData\\Local\\Google\\Cloud SDK\\google-cloud-sdk\\lib\\gcloud.py" compute start-iap-tunnel {INSTANS} %p --listen-on-stdin --project=knada-gcp --zone=europe-north1-b --verbosity=warning
+ ProxyUseFdpass no
+ User {USERNAME}
+
Erstatt {HOSTNAME}, {PROXYCOMMAND} og {USERNAME} med verdiene du får ut av dry-run kommandoen over og lagre filen under ~/.ssh/config
.
+Merk: {USERNAME} skal kun være det før @
i output fra kommandoen over, {HOSTNAME} er det som begynner med compute.
og {INSTANS} som du finner i Knorten under Compute
.
Remote - SSH
i VS Code.Remote - SSH: Connect to host...
og velg så hosten knada-vm
.SSH
under Remote Development
New Connection
SSH Configurations
ved å trykke på tannhjulet til høyre for Connection
feltetSSH Configurations
vinduet trykk +
for å legge til en ny konfigurasjon.OK
Host
settes til knada-vm
User name
settes til brukernavnet på maskinen din lokaltAuthentication type
settes til OpenSSH config and authentication agent
Check Connection and Continue
(dette tar fort litt tid første gangen)Choose IDE and Project
dukker opp sett Project directory
.
+Dette skal settes til ditt hjemmeområde på serveren /home/<brukernavn>
, trykk på browse ikonet til høyre for boksen for å lete det oppDownload and Start IDE
For å optimalisere tilkobling mot VMen kan man bruke SSH multiplexing.
+Dette er kjekt mot KNADA VM for da man slipper å autentisere seg for hver tilkobling.
+Med bruk av ControlPersists
kan man si hvor lenge forbindelsen skal leve etter at man kobler fra siste gang.
+Så hvis du SSH-er inn, skrur av tilkoblingen så vil du kunne starte VSCode innen 5 minutter og den vil kunne gjennbruke samme tilkobling.
+Hvis man ikke ønsker at alle SSH tilkoblinger skal bruke multiplexing kan du bruke legge linjene (minus Host *
) under Host knada-vm
.
Host *
+ ControlMaster auto
+ ControlPersist 5m
+
Se SSH sin egen dokumentasjon for mer informasjon om de forskjellige variablene.
+Vi har tatt i bruk brannmurer for å hindre uønsket trafikk ut av VMene. +Dette er konfigurert via Terraform i knada-gcp, og vi tilbyr ikke en individuell løsning. +Derfor er vi litt mer restriktiv med hva vi åpner for.
+Se allowlisting#knadavm for en oversikt over åpningene vi tilbyr for Knada VM.
+Den virtuelle maskinen kommer med python 3.9 installert, men mangler installasjon av pip.
+Installasjon av pip
på VMen kan gjøres med
sudo apt install python3-pip
+
R kan installeres som følger
+sudo apt update -y && sudo apt install -y r-base
+
Google Cloud Storage
.
+Se eksempelBigQuery
Alle disse kan lastes ned og installeres av deg.
+For å bruke python biblioteker til å lese fra postgres og oracle kreves det at drivere for det er installert på den virtuelle maskinen. +For å gjøre det enkelt for dere å komme i gang har vi lagd to scripts som begge må kjøres med root privilegier.
+Kjør derfor først kommandoen:
+sudo -i
+
Trenger du Postgres, lim inn følgende i terminalen din:
+apt-get update && apt-get install -yq --no-install-recommends libpq-dev
+
apt-get update && apt-get install -yq --no-install-recommends \
+ build-essential \
+ curl \
+ alien \
+ libaio1 \
+ libaio-dev && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
+
+curl https://download.oracle.com/otn_software/linux/instantclient/219000/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm > /tmp/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm
+
+alien -i /tmp/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm && \
+ rm -rf /var/cache/yum && \
+ rm -f /tmp/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm && \
+ echo "/usr/lib/oracle/21.9/client64/lib" > /etc/ld.so.conf.d/oracle-instantclient21.9.conf && \
+ /usr/sbin/ldconfig
+
+PATH=$PATH:/usr/lib/oracle/21.9/client64/bin
+
chmod +x setup_nb.sh
+sudo ./setup_nb.sh
+
Nå vil skriptet installere versjon 21.9 av oracle klienten. Dersom du i stedet ønsker en annen versjon kan editere skriptet over med den versjonen du ønsker. Du finner en liste over tilgjengelige versjoner av oracle klienten her.
+Hvis du skal lese TDV data fra VM i knada-gcp
må du selv installere drivere og biblioteker som er nødvendig.
+Dette kan gjøres som følger:
F:\DVH\TIBCO\drivers\TIB_tdv_drivers_x.x.x_all\apps\odbc\linux64
(erstatt x med ønsket versjon)unixodbc-dev
: sudo apt-get install unixodbc-dev
pyodbc
: pip install pyodbc
Vi oppfordrer brukere av Knast maskiner å bruke uv for oppsett av virtuelle python miljøer og installasjon av pakker.
+Under følger en oppskrift for å sette opp et slikt virtuelt miljø for å kunne kjøre python kode i en isolert kontekst.
+For mer informasjon om uv
, se deres dokumentasjon.
Merk for å installere en annen python versjon må github.com/indygreg/python-build-standalone/releases/download/*
allowlistes for Knast maskinen i datamarkedsplassen
# Setter opp et nytt venv og installerer python 3.11
+uv venv --python 3.11 myvenv
+
+# Aktiverer det virtuelle miljøet i gjeldende shell
+source myvenv/bin/activate
+
Tar her utgangspunkt i at bruker har en requirements fil som heter requirements.in
som inneholder en liste over avhengighetene det er behov for.
# Låser avhengigheter og lagrer i requirements.txt
+uv pip compile requirements.in -o requirements.txt
+
+# Installerer avhengigheter fra requirements.txt
+uv pip sync requirements.txt
+
For å bruke det virtuelle miljøet for notebooken konteksten din velger du Select another kernel
-> Python environments
-> myvenv
.
I Markedsplassen er det mulig å tilrettelegge pseudonymiserte tabeller for sammenkobling. +Du trenger tilgang til minst to pseudonymiserte tabeller registrert på Markedsplassen. +Du spesifiserer hvilke tabeller du ønsker å koble sammen. +Du kan også velge å lage konsistente id-er for personer som har byttet fødselsnummer. +For de tabellene som inneholder fødselsnummer må du oppgi hvilken kolonne disse finnes i. +Tabellene slettes etter 21 dager, med mindre du velger noe annet. +Når du bestiller tabellene skjer følgende:
+BigQuery-dataset
opprettes i et eget Markedsplassen-prosjekt i GCP. Du får tilgang til dette.BigQuery-dataset
Tilganger til vises under "mine tilganger" på Markedsplassen.
+ + + + + + + + + + + + + + + + + +For å komme i gang med analysering tilbyr NADA dokumentasjon på hvordan man kan ta i bruk Jupyter Notebooks og Airflow i teamprosjektene på GCP, se managed Jupyter notebook og managed Airflow for mer informasjon.
+Dersom man har behov for å nå onprem kilder tilbyr vi også analyseverktøy for dette i KNADA.
+KNADA er NADA sin analyseplattform kjørende i GCP med kobling til datakilder i onprem. +Man må ha Naisdevice for å nå tjenester i Knada. +Tilgang til Jupyter Notebooks og Airflow i KNADA bestilles i Knorten.
+Gjennom Knorten oppretter man et team for en eller flere personer. +Man kan så installere Jupyter notebook eller Airflow. +Teamadministreringen skjer igjennom Knorten.
+Hvis du eller noen i teamet ditt ikke tilhører Utvikling og data (U&D) må de manuelt bli lagt til Azure AD-gruppen knada-gw-access
.
+Dette er en gruppe alle kan bli medlem av, og vi ønsker bare å ha en god oversikt over hvem som ikke tilhører U&D.
+Ta kontakt i Slack#nada for bistand.
Metabase er en opensource plattform for moderne datautforskning og visualisering.
+Metabase for interne data i NAV er tilgjengelig på metabase.ansatt.nav.no. +Innloggingen gjøres med SSO.
+Med unntak av all-users@nav.no
så støtter vi ikke at gruppetilganger gitt gjennom Datamarkedsplassen synkroniseres til Metabase. For å få tilgang i Metabase må derfor hver enkelt bruker gis individuell tilgang til datasettet i Datamarkedsplassen. Dette inkluderer også eiere av datasettet.
Dersom teamet legger til datasettet i Metabase (Legg til i metabase
på Datamarkedsplassen), vil det være tilgjengelig for de samme som har tilgang til datasettet på Datamarkedsplassen.
+Når datasettet legges til i metabase, vil det opprettes en database (I
i figuren under) og en collection (II
i figuren under) i metabase.
+Begge deler er tilgangsbegrenset.
+Tilgangsstyringen gjelder både for å lese Dashboard og for å lage dem.
flowchart BT
+subgraph Dataprodukt
+subgraph Datasett 1
+A{Tilganger}
+B[(BigQuery-tabell)]
+end
+end
+
+subgraph Metabase
+subgraph II: Tilgangsstyrt collection
+C[Dashboard]
+D[Questions]
+end
+E[(I: Database)]
+F{Metabase-tilganger}
+end
+
+subgraph Annen collection
+G[Questions]
+H[Dashboard]
+
+end
+
+A --Tilganger til Metabase er <br> er de samme som i BigQuery--> F
+B --> E
+F --> D
+D --> C
+G --Personer med tilgang til <br> collectionen har tilgang til <br> *resultatet* fra questionen <br> om den ligger i samme collection. <br> De kan ikke endre questionen <br> eller se data i underliggende tabell--> H
+E --> F
+F --Kan kun bruke data <br> andre steder om personen <br> har tilgang--> G
+Datasett som er åpne for alle i NAV (all-users@nav.no
) synkroniseres automatisk til metabase og åpnes for alle brukere.
Tilgangsbegrensede elementer i åpent tilgjengelige dashboards vil fortsatt være tilgangsbegrenset. +Det betyr at om du inkluderer data som kun er tilgjengelig for ditt team inn i et åpent dashboards, vil folk utenfor teamet få beskjed om at de ikke har tilgang til akkurat det dataene.
+Tilgangen knyttet til mappen elementene er lagret i bestemmer hvem som kan se dem. Metabase har to toppnivå:
+Det er mulig å flytte elementer mellom mapper etter behov, men husk at det er tilgangen til mappen som bestemmer hvem som kan se spørsmål og dashboard.
+Når et datasett legges til i metabase vil appen gi hvert felt en semantisk type som den tror passer. Hvis metabase gjetter feil kan det føre til problemer som at du ikke kan summere tall, filtrere på en kategori eller bruke tidsfunksjoner. Dette kan endres i adminmenyen:
+Admin settings
. Her finner du alle datasett du har tilgang til via Datamarkedsplassen.Er du usikker på hvilken type som passer kan du prøve deg fram eller velge en av disse:
+Entity name
eller Category
Creation timestamp
eller Creation date
Quantity
Dersom du gjør endringer på skjemaet til en tabell i BigQuery vil det ta inntil en time før metabase oppdager endringen av seg selv. Du kan trigge denne synken selv via adminmenyen:
+Admin settings
. Her finner du alle datasett du har tilgang til via Datamarkedsplassen.Re-scan this table
.Q: Jeg har nettopp lagt til en ny kolonne i BigQuery-tabellen min. Hvorfor dukker den ikke opp i metabase?
+A: Metabase kjører en synk hver time for å sjekke om skjemaet har endret seg. Det vil derfor ta litt tid før det dukker opp i metabase automatisk. Dersom du trenger det i metabase med en gang, følg oppskriften for synkronisering av skjemaendringer over.
Q: Filter-verdier i metabase er ikke oppdatert?
+A: Metabase kjører en daglig sync mot hele tabellen i BigQuery for å finne ut av hvilke verdier som finnes. Dette brukes bl.a. til å generere options
i filter. Disse vil også oppdateres med Re-scan this table
som forklart over.
Q: Hvorfor får jeg ikke joinet datasett som er åpent tilgjengelig?
+A: Du kan det! Men du må gjøre det med SQL query
. Et alternativ er å joine tabeller i BigQuery, lage et nytt datasett og legge dette til i Metabase.
Q: Kan jeg koble sammen datasett som er tilgangsbegrenset?
+A: Slik Metabase er satt opp, er det en unik service-bruker per tilgangsbegrensede datasett.
+Det er mulig å gjøre dette i metabase ved å be datasetteier om å gi tilgang til service-account, men vi anbefaler å lage et view i BigQuery.
Q: Jeg har tilgang til et datasett i Datamarkedsplassen gjennom å være medlem av en AD-gruppe og får lest data direkte fra BigQuery, hvorfor har jeg da ikke tilgang til samme datasett i Metabase?
+A: I metabase leses data fra kilden med en service-bruker og ikke din personlige bruker.
+Vi har ikke synkronisering av AD-grupper til Metabase, så for å få samme tilgang i Metabase må hver enkelt person legges til manuelt for å inkluderes i korrekt tilgangsgruppe i Metabase.
++nbstripout er et verktøyet som sørger for at output celler i Jupyter notebooks utelates fra Git commits.
+
For å unngå at output celler fra Jupyter notebooks blir pushet sammen med kode til Github anbefaler vi å installere nbstripout. +Dette verktøyet må installeres alle stedet du har repoet sjekket ut.
+For å ta dette i bruk i din notebook:
+pip install nbstripout --user
nbstripout --install --global
fra repoet ditt lokaltVi anbefaler alle å lære seg å bruke sin personlige bruker fra Jupyter notebooks, dette for å sikre seg at man bruker kun de tilgangene man selv skal ha.
+gcloud auth login --update-adc
Etter å ha utført stegene over vil du i din notebook kunne jobbe med dine private Google credentials mot kilder. +Denne tilgangen er kun midlertidig, og man må gjøre dette hver dag.
+Warning
+Dette gjelder kun for managed notebooks. +Bruker du KNADA notebook, se autentisering med personlig bruker.
+En fersk managed notebook vil automatisk autentisere seg mot GCP-tjenester med service accountens credentials. +Det betyr at man er tilkoblet GCP med en service accounten ut av boksen når man starter opp en notebook. +Bruker du denne service accounten, så er det denne brukeren som må få tilgang til kildene du skal snakke med.
+Hvis du på et tidspunkt har logget på GCP med din personlige bruker fra notebooken, og har behov for å bytte tilbake kan du følge oppskriften nedenfor.
+gcloud auth list
.gcloud config set account <eposten til service accounten din>
.gcloud auth application-default revoke --account <din personlige nav-epost>
.Ved å justere arraysize
og prefetchrows
kan spørringer mot Oracle databaser onprem forbedres markant!
+Se dokumentasjonen til oracledb-biblioteket for mer informasjon.
Du kan installere extensions til Jupyter selv på samme måte som du installere Python pakker. +Etter man har installert en extension må du stoppe notebooken og starte den på nytt.
+Eksempel for Git-extension
i KNADA:
pip install --upgrade jupyterlab jupyterlab-git --user
+
Dependabot støtter per i dag ikke R, denne oppskriften funker kun for de som bruker kun språkene Ruby, JavaScript, Python, PHP, Dart, Elixir, Elm, Go, Rust, Java og .NET.
+Vi oppfordrer Jupyter notebook brukere til å ha en requirements.txt
fil med Python-bibliotekene som dere selv bruker i et Github-repo.
+Alle repoer i navikt
har automatisk aktivert Github Advanced Security inkludert Dependabot.
+For å enable security scan av en requirements.txt fil, må man lage en dependabot.yml
fil i repoet under mappen .github
, altså:
+
.github
+└── dependabot.yml
+requirement.txt
+
Under er et eksempel på en slik fil som vil scanne en requirements.txt fil på rotnivå i repo ukentlig: +
version: 2
+updates:
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ pip:
+ patterns:
+ - '*'
+
Dersom man bygger egne Dockerimages for Jupyter eller Airflow tilbyr Dependabot også automatisk scanning etter sårbarheter for disse.
+En kan også aktivere CodeQL for repoet som analyserer koden din og genererer alerts ved sårbarheter. +Se her for informasjon om oppsett av CodeQL.
+Det finnes flere måter å autentisere seg mot Github, men vi anbefaler å enten bruke SSH-nøkler eller fine-grained personal access tokens (PAT).
+Er du usikker på hva du trenger så anbefaler vi at du starter med fine-grained PAT med en varighet på 7 dager. +Da har du nok tid til å utforske Jupyter, men ingen risiko for at dine tilgangsnøkler blir liggende til evig tid på Jupyter hvis du glemmer å rydde opp.
+Ved å bruke SSH-nøkler så lager man et nøkkelpar, hvor Github får din offentlige nøkkel, og man har sin private nøkkel lagret i Jupyter notebooken sin. +Vi anbefaler på det sterkeste å ha en egen nøkkel for Jupyter, da kan man enkelt trekke tilbake tilgangen hvis den blir slettet, eller havner på avveie.
+Du kan følge Github sin Generating a new SSH key, eller kjørende kommandoen nedenfor. +Vi anbefaler å bruke passord på SSH nøkkelen, dette er påkrevd for SSH-nøkler på lokal maskin.
+ssh-keygen -t ed25519 -C "din_epost_email@nav.no"
+
Etter at du har generet et eget nøkkelpar må du legge den offentlige delen inn hos Github.
+Du kan følge Github sin Adding a new SSH key to your account, eller gå direkte til SSH and GPG keys, og trykke på New SSH key
.
Etterpå kan du bruke git
som vanlig og klone ned med SSH
-adressen (git@github.com:navikt/ditt-repo.git).
Info
+SSH-nøkler må ligge i katalogen ~/.ssh
og kun lesbar av deg.
+Sette filrettigheter med chmod 600 ~/.ssh/id_ed25519
.
Som default så har vi kun lagt til brannmuråpning mot Github på port 443. Du kan enten legge til en åpning mot +Github på port 22, eller følge denne guiden: https://docs.github.com/en/authentication/troubleshooting-ssh/using-ssh-over-the-https-port
+Personal access tokens brukes for å lage et token med en bestemt varighet, som gir alle som har ditt token mulighet til å koble seg til Github. +Med fine-grained tokens kan man spesifisere mer detaljert hva man skal ha tilgang til, for eksempel spesifisere hvilke Github repo man skal ha tilgang til.
+Gå til New fine-grained personal access token for å komme i gang. +Merk at du vil ikke kunne hente ut en PAT etter den har blitt generert, så hvis du mister den så er det bare å rotere tokenet.
+For å bruke PAT i Jupyter kan du opprette filen .netrc
i ditt hjemmeområde i Jupyter med følgende innhold:
machine github.com login <PAT>
+
Etterpå kan du bruke git
som vanlig og klone ned med HTTPS
-adressen (https://github.com/navikt/ditt-repo.git)
En Jupyter notebook er analytikerens fremste redskap for å utforske og analysere data. +Plattformen støtter innlesing av dataprodukter registrert på Datamarkedsplassen og andre kilder, eksempelvis datavarehus (via KNADA).
+Jupyter notebooks er et godt verktøy for å utforske og finne innsikt i data. +Deling av innsikt med andre kan enkelt gjøres med en datafortelling.
+ + + + + + + + + + + + + + + + + +KNADA notebooks bestilles gjennom Knorten. +Disse notebookene kjører i et managed Kubernetes cluster i GCP som driftes av NADA.
+For KNADA notebooks vil det kun være de Python-pakkene som blir lagret under user
-stien som blir persistert når Notebook-en slås av.
+Derfor er det nødvendig å bruke --user
flagget når du installere Python-pakker.
pip install google-cloud-bigquery --user
+
Glemmer du å bruke --user
, så blir pakkene installert globalt, og de vil forsvinne når notebook-en din blir skrudd av.
+Har du behov for å ha globalt installerte pakker anbefaler vi at du bruker ditt eget Jupyter notebook image.
For å spare ressurser vil en Jupyter notebook automatisk blir skrudd av etter en times inaktivitet.
+Har man behov for lengre levetid kan man spesifisere dette gjennom Knorten i feltet Cull Timeout
.
Du kan se ressursbruken din i Grafana/Jupyter notebook utilization.
+Når man logger inn i Jupyter så får man mulighet til å velge hvilket Python miljø man ønsker i sin notebook. +Vi følger Python Release Cycle for imagene våre. +Det betyr at vi lager et image per Python-versjon som ikke har statusen end-of-life, og som også er støttet av Jupyter (se Docker Hub). +Samtlige av image-ene man kan velge mellom kommer med drivere for oracle, postgres og TDV installert.
+Har du behov for noe mer enn det vi tilbyr ut av boksen kan du lage ditt eget Jupyter notebook image. +Ta gjerne utgangspunkt i vårt image med den Python-versjonen du ønsker. +Gjør du det får du drivere for oracle, postgres og TDV, samt de vanligste kommandolinjeverktøyene som er hendige å ha i en notebook allerede installert.
+Når du har laget et image kan du selv spesifisere at det er dette imaget som skal brukes for teamet ditt i Knorten.
+NB: Hvis du bygger image lokalt på en nyere Mac så er det viktig at du bygger imaget for riktig plattform.
+Legg til --platform linux/amd64
i docker build
kommandoen.
La oss si at du har en requirements.txt
fil med Python-pakker som under:
backoff==2.0.1
+oracledb>=1.4.2
+datastory>=0.1.12
+google-cloud-bigquery>3.0.0
+google-cloud-storage==2.4.0
+great-expectations==0.15.34
+influxdb==5.3.1
+
Da kan du med følgende Dockerfile
installere disse pakkene når du bygger ditt image:
FROM ghcr.io/navikt/knada-images/jupyter:2023-06-16-738148c-3.10
+
+USER root
+
+COPY requirements.txt requirements.txt
+RUN pip install -r requirements.txt
+
+USER $NB_USER
+
Du kan selv velge om du ønsker å lagre imaget ditt i Google Artifact Registry (GAR) eller i Github Container Registry (GHCR). +I dette navikt/knada-image-eksempel er det eksempler på hvordan å bygge et docker image og pushe til begge to docker repositories. +Begge pipelinene bygger image med samme Dockerfile og requirements.txt.
+Når du så har bygget image ditt finner du det igjen ved å:
+Du kan lese mer om hemmeligheter under Google Secret Manager.
+For manuelt å restarte en Jupyter notebook går man til kontrollpanelet ved å velge File
-> Hub Control Panel
-> Stop My Server
. Etter at serveren er stoppet vil man så kunne trykke Start My Server
.
Dersom jupyterhubben din har fryst seg og du ikke har mulighet til å gjøre det over kan du gå direkte til kontroll panelet hvis du går til stien /hub/home
i nettleseren
Man må eksplisitt oppgi hvilke hoster man ønsker å snakke med fra notebooks i KNADA. Dette spesifiserer du gjennom knorten.
+Tilsvarende som for airflow legger du inn hostnavn og port på formatet hostnavn:port
. Dersom man ikke angir port vil vi bruke 443
som standardport.
+Vi har en controller kjørende i KNADA som vil lage en NetworkPolicy
som tillater trafikk ut fra notebooken mot de hostene som legges til.
+Når notebooken din slås av vil tilgangene bli fjernet.
++Vertex AI Workbench is a Jupyter notebook-based development environment for the entire data science workflow. +You can interact with Vertex AI and other Google Cloud services from within a Vertex AI Workbench instance's Jupyter notebook.
+
Du finner den offisielle dokumentasjonen for managed notebooks hos cloud.google.com.
+Nedenfor har vi et forslag til innstillinger som passer for de fleste brukere.
+Det er mange ulike konfigurasjonsmuligheter for notebooks og man står fritt til å sette de opp med de innstillingene man selv ønsker, men merk særlig det som presiseres under maskintype og GPU.
+Det er også kostnadsbesparende å skru av notebooken på slutten av arbeidsdagen med mindre man trenger å ha noe kjørende utenfor arbeidstid.
+Team-prosjekt
+Dette vil være en notebook server som settes opp i GCP prosjektet til teamet ditt. +Man kan til enhver tid se hvilke prosjekter man er medlem av ved å gå til GCP Billing.
+Det er du som vet best hva du trenger, derfor er det ingen begrensninger på hva du kan velge av maskin og GPU. +Bare husk at det kan bli veldig kostbart hvis du lar en maskin (med mye minne og GPU) stå uten at den blir brukt.
+Merk også at notebooks som kjører i regionen north/Finland ikke har tilgang til GPU.
+Vi anbefaler alle å stoppe maskinene utenom arbeidstid og når de ikke er i bruk. +Stopping er ikke det samme som sletting, så alt er der når du starter den opp igjen.
+Velg maskinen i Vertex AI i oversikten og trykk STOP i toppen.
+Har du en maskin du ikke trenger lenger, så kan du slette den.
+Velg maskinen i Vertex AI i oversikten og trykk DELETE i toppen.
+For å bruke python biblioteker til å lese fra postgres og oracle kreves det at drivere for det er installert på notebook serveren. Dersom man bruker en Knada Notebook så kommer det miljøet man jobber i der allerede med disse driverne installert. Men dersom man jobber i en GCP Managed Notebook må man manuelt installere disse driverne.
+For å gjøre det enkelt for dere å komme i gang har vi lagd to scripts som begge må kjøres med root privilegier.
+Kjør derfor først kommandoen: +
sudo -i
+
Trenger du Postgres, lim inn følgende i terminalen din: +
apt-get update && apt-get install -yq --no-install-recommends libpq-dev
+
apt-get update && apt-get install -yq --no-install-recommends \
+ build-essential \
+ curl \
+ alien \
+ libaio1 \
+ libaio-dev && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
+
+curl https://download.oracle.com/otn_software/linux/instantclient/215000/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm > /tmp/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm
+
+alien -i /tmp/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm && \
+ rm -rf /var/cache/yum && \
+ rm -f /tmp/oracle-instantclient-basic-21.9.0.0.0-1.x86_64.rpm && \
+ echo "/usr/lib/oracle/21.9/client64/lib" > /etc/ld.so.conf.d/oracle-instantclient21.9.conf && \
+ /usr/sbin/ldconfig
+
+PATH=$PATH:/usr/lib/oracle/21.9/client64/bin
+
Nå vil skriptet installere versjon 21.9 av oracle klienten. Dersom du i stedet ønsker en annen versjon kan editere skriptet over med den versjonen du ønsker. Du finner en liste over tilgjengelige versjoner av oracle klienten her.
+Imager vi tilbyr for Jupyterhub og Airflow kommer uten TDV driver. +Er du avhengig av denne driveren er du nødt til å enten bygge et custom image med driveren inkludert eller (for jupyterhub) laste den opp til hjemmeområde på Jupyterhub serveren.
+Driveren finner du via utviklerimage ved å gå til F:\DVH\TIBCO\drivers\TIB_tdv_drivers_x.x.x_all\apps\odbc\linux64
(erstatt x med ønsket versjon).
For Airflow og dersom du ønsker å bygge eget image med driveren for Jupyterhub kan du legge til som følger i Dockerfilen:
+# Add TDV ODBC driver and set env
+COPY TDV/driver/libcomposite86_x64.so /opt/TDV/driver/libcomposite86_x64.so
+RUN chown -R jovyan:users /opt/TDV
+ENV TDV_ODBC_DRIVER /opt/TDV/driver/libcomposite86_x64.so
+
Merk: eksempelet over forutsetter at TDV driveren er inkludert i github repoet hvor imaget bygges på stien TDV/driver/libcomposite86_x64.so
import os
+import pyodbc
+import pandas as pd
+
+def create_tdv_conn(server: str, port: int, datasource: str, uid: str, pwd: str):
+ conn_str = "DRIVER={" + os.environ["TDV_ODBC_DRIVER"] + "};DOMAIN=ADEO;SERVER=" + server + ";PORT=" + str(port) + ";DATASOURCE=" + datasource + ";UID=" + uid + ";PWD=" + pwd
+ conn = pyodbc.connect(conn_str)
+ conn.setencoding(encoding='utf-8')
+ conn.setdecoding(pyodbc.SQL_WCHAR, encoding='utf-8')
+ conn.setdecoding(pyodbc.SQL_CHAR, encoding='utf-8')
+ return conn
+
+conn = create_tdv_conn(server="tdv-p.adeo.no", port=1234, datasource="Plattform", uid=os.environ["TDV_USER"], pwd=os.environ["TDV_PASSWORD"])
+
+df = pd.read_sql("SELECT * FROM schema.table", conn)
+df.head()
+
Kode eksempelet forutsetter at miljøvariabelen TDV_ODBC_DRIVER er satt til å peke på stien til TDV driveren. Dersom du ikke har bygget eget image og i stedet bare har lastet driveren opp til hjemmeområdet (/home/jovyan
) på notebook serveren må du i stedet sette denne stien direkte.