
Nyligen meddelade ett vÀlkÀnt företag att de övergÄr till ARM-arkitektur för sin serie bÀrbara datorer. NÀr jag hörde denna nyhet kom jag att tÀnka pÄ att jag vid ett flertal tillfÀllen hade kikat pÄ priserna för EC2 i AWS och la mÀrke till Graviton-processorerna med ett vÀldigt attraktivt pris. Problemet var givetvis att det var ARM. DÄ hade jag ingen aning om att ARM faktiskt Àr ganska respektingivande...
För mig har denna arkitektur alltid varit förknippad med mobil- och andra IoT-prylar. ''Riktiga'' servrar pĂ„ ARM kĂ€nns lite ovanligt, ja till och med bisarrt... Men en ny tanke satte sig i huvudet, sĂ„ jag bestĂ€mde mig en weekend för att kolla vad som faktiskt gĂ„r att köra pĂ„ ARM idag. Jag ville börja med det som kĂ€ndes mest bekant â ett Kubernetes-kluster. Och inte bara nĂ„got slags ''kluster'', utan verkligen en riktig installation, sĂ„ att det skulle bli sĂ„ likt produktion som möjligt.
Enligt min plan skulle klustret vara tillgÀngligt frÄn internet, köra en viss webapplikation och ha minst nÄgon form av övervakning. För att förverkliga denna idé behövs ett par (eller fler) Raspberry Pi, minst i modellen 3B+. Plattformen för experimenten skulle kunna vara AWS, men jag var mer intresserad av just ''hallonen'' (som ÀndÄ stod oanvÀnda). SÄ vi kommer att sÀtta upp ett Kubernetes-kluster pÄ dem med Ingress, Prometheus och Grafana.
Förberedelse av ''hallonen''
Installation av OS och SSH
Jag lÀt mig inte överdrivet plÄgas av valet av operativsystem: jag tog helt enkelt den senaste Raspberry Pi OS Lite med . DÀr finns Àven , alla ÄtgÀrder dÀrifrÄn mÄste utföras pÄ alla noder i det framtida klustret. Vidare behövs följande manövrer (ocksÄ pÄ alla noder).
Efter att ha anslutit en monitor och tangentbord, mÄste nÀtverket och SSH konfigureras först:
- För att klustret ska fungera krÀvs en statisk IP-adress pÄ master-noden, medan arbetande noder kan fÄ adresser pÄ valfritt sÀtt. Jag valde att anvÀnda statiska adresser överallt för att förenkla konfigurationen.
- En statisk adress kan konfigureras i operativsystemet (i filen
/etc/dhcpcd.conffinns ett lĂ€mpligt exempel) eller genom att fĂ€sta lease i DHCP-servern pĂ„ den anvĂ€nda (i mitt fall â hemman) routern. - ssh-server aktiveras enkelt via raspi-config (interfacing options â ssh).
Efter detta kan du logga in via SSH (standardinloggning â pi, och lösenordet â raspberry eller det som Ă€ndrades) och fortsĂ€tta konfigurationerna.
Ăvriga instĂ€llningar
- Vi stÀller in vÀrdnamnet. I mitt exempel kommer vi att anvÀnda
pi-controlochpi-worker. - Vi kontrollerar att filsystemet har utökats till hela disken (
df -h /). Vid behov kan det utökas med hjÀlp av raspi-config. - Vi Àndrar det förinstÀllda lösenordet för anvÀndaren i raspi-config.
- Vi stÀnger av swap-filen (detta Àr ett krav frÄn Kubernetes; om du Àr intresserad av detaljer om detta, se ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Vi uppdaterar paketen till de senaste versionerna:
apt-get update && apt-get dist-upgrade -y - Vi installerar Docker och ytterligare paket:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentVid installation
iptables-persistentmĂ„ste konfigureras för att spara iptables-instĂ€llningarna för ipv4, och i filen/etc/iptables/rules.v4â lĂ€gg till regler i kedjanFORWARD, sĂ„ hĂ€r:# Generated by xtables-save v1.8.2 on Sun Jul 19 00:27:43 2020 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A FORWARD -s 10.1.0.0/16 -j ACCEPT -A FORWARD -d 10.1.0.0/16 -j ACCEPT COMMIT - Det Ă€r bara att starta om.
Nu Àr allt klart för installationen av Kubernetes-klustret.
Installation av Kubernetes
Vid detta stadie har jag medvetet skjutit upp alla mina och vÄra företagslösningar för automatisk installation och konfiguration av K8s-kluster. IstÀllet kommer vi att anvÀnda den officiella dokumentationen med (lite kompletterad med kommentarer och förkortningar).
Vi lÀgger till Kubernetes-repositoriet:
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get updateVidare föreslÄr dokumentationen att installera CRI (container runtime interface). Eftersom Docker redan Àr installerat, gÄr vi vidare och installerar huvudkomponenterna:
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni Vid installationen av huvudkomponenterna lade jag ocksÄ till kubernetes-cni, som Àr nödvÀndig för klustrets funktion. HÀr finns en viktig punkt: paketet kubernetes-cni skapar av nÄgon anledning inte en standardkatalog för instÀllningarna för CNI-grÀnssnitten, sÄ jag mÄste skapa den manuellt:
mkdir -p /etc/cni/net.dFör att fungera korrekt med nÀtverksbackend som kommer att diskuteras nedan, mÄste vi installera en plugin för CNI. Jag valde den bekvÀma och vÀlkÀnda portmap-plugin (se hela listan i ):
curl -sL https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-arm-v0.7.5.tgz | tar zxvf - -C /opt/cni/bin/ ./portmapKonfigurering av Kubernetes
Noden med control plane
Installationen av sjÀlva klustret görs ganska enkelt. För att pÄskynda denna process och kontrollera att Kubernetes-bilderna Àr tillgÀngliga kan vi först köra:
kubeadm config images pullNu genomför vi sjĂ€lva installationen â initierar klustrets control plane:
kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certsObservera att subnÀt för tjÀnster och poddar inte fÄr överlappa varandra eller med befintliga nÀtverk.
I slutet kommer vi att se ett meddelande om att allt Àr okej, och vi fÄr ocksÄ rÄd om hur man ansluter arbetsnoder till kontrollplanet:
Ditt Kubernetes kontrollplan har initierats framgÄngsrikt!
För att börja anvÀnda din kluster mÄste du köra följande som en vanlig anvÀndare:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Du bör nu distribuera ett pod-nÀtverk till klustret.
Kör "kubectl apply -f [podnetwork].yaml" med ett av alternativen som anges pÄ:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Du kan nu ansluta valfritt antal kontrollplane-noder genom att köra följande kommando pÄ varje som root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Observera att certificate-key ger Ätkomst till klusterets kÀnsliga data, hÄll det hemligt!
Som en sÀkerhetsÄtgÀrd kommer uppladdade certifikat att raderas om tvÄ timmar; Om nödvÀndigt kan du anvÀnda
"kubeadm init phase upload-certs --upload-certs" för att ladda om certifikat i efterhand.
Sedan kan du ansluta valfritt antal arbetsnoder genom att köra följande pÄ varje som root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Vi kommer att följa rekommendationerna för att lÀgga till konfigurationen för anvÀndaren. Jag rekommenderar ocksÄ att du direkt lÀgger till autocompletions för kubectl:
kubectl completion bash > $HOME/.kube/completion.bash.inc
printf "
# Kubectl shell completion
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileVid det hÀr laget kan vi redan se den första noden i klustret (Àven om den inte Àr redo Àn):
root@pi-control:~# kubectl get no
NAME STATUS ROLES AGE VERSION
pi-control NotReady master 29s v1.18.6NĂ€tverkskonfiguration
Vidare, som nÀmnts i meddelandet efter installationen, kommer vi att behöva installera ett nÀtverk i klustret. Dokumentationen erbjuder val mellan Calico, Cilium, contiv-vpp, Kube-router och Weave Net⊠HÀr avvek jag frÄn den officiella instruktionen och valde ett mer bekant och förstÄeligt alternativ: i host-gw-lÀge (mer information om tillgÀngliga backendar finns i ).
Att installera det i klustret Ă€r ganska enkelt. Först â ladda ner manifesten:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml DÀrefter Àndrar vi instÀllningarna sÄ att typen blir vxlan till host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml⊠och poddarnas subnĂ€t â frĂ„n standardvĂ€rdet till det som angavs vid initiering av klustret:
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlEfter det skapar vi resurser:
kubectl create -f kube-flannel.yml Klart! Efter en stund kommer den första K8s-noden att byta status Redo:
NAME STATUS ROLES AGE VERSION
pi-control Ready master 2m v1.18.6LĂ€gg till en arbetsnod
Nu kan du lÀgga till en worker. För detta, efter att ha installerat Kubernetes enligt det ovan beskrivna scenariot, behöver du bara köra det tidigare erhÄllna kommandot:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050DÀrmed kan vi sÀga att klustret Àr klart:
root@pi-control:~# kubectl get no
NAME STATUS ROLES AGE VERSION
pi-control Ready master 28m v1.18.6
pi-worker Ready 2m8s v1.18.6Jag hade bara tvÄ Raspberry Pi tillgÀngliga, sÄ jag ville inte ge bort en av dem bara till kontrollplanet. DÀrför tog jag bort den automatiskt installerade tainten frÄn noden pi-control genom att köra:
root@pi-control:~# kubectl edit node pi-control⊠och tog bort raderna:
- effect: NoSchedule
key: node-role.kubernetes.io/masterFyllning av klustret med minimikrav
Först och frÀmst behöver vi Helm. SjÀlvklart kan man göra allt utan det, men Helm lÄter dig bokstavligen konfigurera vissa komponenter utan att redigera filer. Och i sjÀlva verket Àr det bara en binÀr fil som "inte ber om bröd".
SÄ, vi besöker i avsnittet docs/installation och kör kommandot dÀrifrÄn:
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashEfter detta lÀgger vi till chart-repositoriet:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Nu installerar vi infrastrukturkomponenterna enligt plan:
- Ingress controller;
- Prometheus;
- Grafana;
- cert-manager.
Ingress controller
Den första komponenten â Ingress controller â installeras ganska enkelt och Ă€r redo att anvĂ€ndas direkt. För detta rĂ€cker det med att gĂ„ till och köra installationskommandot dĂ€rifrĂ„n:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlMen vid detta tillfÀlle började "hallonen" att strula och kÀmpa med diskens IOPS. PoÀngen Àr att mÄnga resurser installeras tillsammans med Ingress-kontrollern, mÄnga API-förfrÄgningar görs, och dÀrmed skrivs mycket data till etcd. Sammanfattningsvis, antingen Àr minneskortet av klass 10 inte sÀrskilt effektivt, eller sÄ saknas det allmÀnt SD-kort för en sÄdan belastning. Trots allt startade det efter ungefÀr 5 minuter.
Ett namespace skapades och dÀr dök kontrollern och allt som behövdes upp:
root@pi-control:~# kubectl -n ingress-nginx get pod
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-2hwdx 0/1 Completed 0 31s
ingress-nginx-admission-patch-cp55c 0/1 Completed 0 31s
ingress-nginx-controller-7fd7d8df56-68qp5 1/1 Running 0 48sPrometheus
De följande tvÄ komponenterna Àr ganska enkla att installera via Helm frÄn chart-repot.
Vi hittar Prometheus, skapar vi ett namespace och installerar i det:
helm search repo stable | grep prometheus
kubectl create ns monitoring
helm install prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"}Som standard bestÀller Prometheus tvÄ diskar: för data frÄn Prometheus sjÀlv och för data frÄn AlertManager. Eftersom det inte skapades en storage class i klustret, kommer diskarna inte att bestÀllas och pod'arna kan inte startas. För bare metal-installationer av Kubernetes anvÀnder vi vanligtvis Ceph rbd, men i fallet med Raspberry Pi Àr det uppenbarligen överdrivet.
DÀrför skapar vi en enkel local storage pÄ hostpath. Manifesten för PV (persistent volume) för prometheus-server och prometheus-alertmanager har sammanstÀllts i filen prometheus-pv.yaml pÄ . Katalogen för PV mÄste skapats i förvÀg pÄ disken för den nod som vi vill binda Prometheus till: i exemplet har vi angett nodeAffinity utifrÄn hostname pi-worker och dÀr har mappar skapats /data/localstorage/prometheus-server och /data/localstorage/prometheus-alertmanager.
Vi laddar ner (klonar) manifestet och lÀgger till det i Kubernetes:
kubectl create -f prometheus-pv.yamlVid det hÀr steget stötte jag första gÄngen pÄ problemet med ARM-arkitekturen. Kube-state-metrics, som som standard installeras i Prometheus-chartet, vÀgrade att starta. Den gav ett felmeddelande:
root@pi-control:~# kubectl -n monitoring logs prometheus-kube-state-metrics-c65b87574-l66d8
standard_init_linux.go:207: exec user process caused "exec format error"Problemet Àr att kube-state-metrics anvÀnder en bild frÄn CoreOS-projektet, som inte byggs för ARM:
kubectl -n monitoring get deployments.apps prometheus-kube-state-metrics -o=jsonpath={.spec.template.spec.containers[].image}
quay.io/coreos/kube-state-metrics:v1.9.7Jag var tvungen att googla lite och hitta, till exempel, . För att anvÀnda den uppdaterar vi releasen och anger vilken bild som ska anvÀndas för kube-state-metrics:
helm upgrade prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"} --set kube-state-metrics.image.repository=carlosedp/kube-state-metrics --set kube-state-metrics.image.tag=v1.9.6Vi kontrollerar att allt har startat:
root@pi-control:~# kubectl -n monitoring get po
NAME READY STATUS RESTARTS AGE
prometheus-alertmanager-df65d99d4-6d27g 2/2 Running 0 5m56s
prometheus-kube-state-metrics-5dc5fd89c6-ztmqr 1/1 Running 0 5m56s
prometheus-node-exporter-49zll 1/1 Running 0 5m51s
prometheus-node-exporter-vwl44 1/1 Running 0 4m20s
prometheus-pushgateway-c547cfc87-k28qx 1/1 Running 0 5m56s
prometheus-server-85666fd794-z9qnc 2/2 Running 0 4m52sGrafana och cert-manager
För diagram och dashboards installerar vi Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}I slutet av utdata kommer vi att visa hur man fÄr ett lösenord för Ätkomst:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoFör att bestÀlla certifikat installerar vi cert-manager. För installationen vÀnder vi oss till , som erbjuder motsvarande kommandon för Helm:
helm repo add jetstack https://charts.jetstack.io
helm install
cert-manager jetstack/cert-manager
--namespace cert-manager
--version v0.16.0
--set installCRDs=trueFör sjĂ€lvsignerade certifikat för hemmet Ă€r detta helt tillrĂ€ckligt. Om du istĂ€llet behöver fĂ„ samma Let's Encrypt, mĂ„ste du Ă€ven konfigurera en cluster issuer. Mer information om detta finns i vĂ„r artikel â».
Jag valde sjÀlv alternativet frÄn , och beslöt att staging-alternativet frÄn LE skulle vara tillrÀckligt. Vi Àndrar e-posten i exemplet, sparar det i en fil och lÀgger till det i klustret ():
kubectl create -f cert-manager-cluster-issuer.yamlNu kan man bestÀlla ett certifikat, till exempel för Grafana. För detta krÀvs en domÀn och Ätkomst till klustret utifrÄn. Jag har en domÀn, och jag har konfigurerat portarna 80 och 443 genom att vidarebefordra dem pÄ min hemrouter enligt den skapade tjÀnsten ingress-controller:
kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.2.206.61 80:31303/TCP,443:30498/TCP 23dI det hÀr fallet vidarebefordras port 80 till 31303 och 443 till 30498. (Portarna genereras slumpmÀssigt, sÄ ni kommer att ha andra.)
HÀr Àr ett exempel pÄ ett certifikat ():
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: grafana
namespace: monitoring
spec:
dnsNames:
- grafana.home.pi
secretName: grafana-tls
issuerRef:
kind: ClusterIssuer
name: letsencrypt-stagingVi lÀgger till det i klustret:
kubectl create -f cert-manager-grafana-certificate.yamlEfter detta kommer det att dyka upp en Ingress-resurs genom vilken valideringen kommer att genomföras av Letâs Encrypt:
root@pi-control:~# kubectl -n monitoring get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
cm-acme-http-solver-rkf8l grafana.home.pi 192.168.88.31 80 72s
grafana grafana.home.pi 192.168.88.31 80 6d17h
prometheus-server prometheus.home.pi 192.168.88.31 80 8d NÀr valideringen har genomförts kommer vi att se att resursen certificate Àr klar, och i den angivna hemligheten grafana-tls finns certifikatet och nyckeln. Du kan genast kontrollera vem som utfÀrdade certifikatet:
root@pi-control:~# kubectl -n monitoring get certificate
NAME READY SECRET AGE
grafana True grafana-tls 13m
root@pi-control:~# kubectl -n monitoring get secrets grafana-tls -ojsonpath="{.data['tls.crt']}" | base64 -d | openssl x509 -issuer -noout
issuer=CN = Fake LE Intermediate X1LÄt oss gÄ tillbaka till Grafana. Vi behöver göra nÄgra justeringar av dess Helm-release, genom att Àndra instÀllningar för TLS enligt det skapade certifikatet.
För detta hÀmtar vi diagrammet, justerar det och uppdaterar frÄn den lokala katalogen:
helm pull --untar stable/grafana Redigerar i filen grafana/values.yaml TLS-parametrar:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi HÀr kan vi ocksÄ direkt konfigurera den installerade Prometheus som datasource:
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:80
access: proxy
isDefault: trueNu uppdaterar vi Grafana-diagrammet frÄn den lokala katalogen:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Vi kontrollerar att Ingress grafana har fÄtt port 443 och har tillgÄng över HTTPS:
root@pi-control:~# kubectl -n monitoring get ing grafana
NAME CLASS HOSTS ADDRESS PORTS AGE
grafana grafana.home.pi 192.168.88.31 80, 443 63m
root@pi-control:~# curl -kI https://grafana.home.pi
HTTP/2 302
server: nginx/1.19.1
date: Tue, 28 Jul 2020 19:01:31 GMT
content-type: text/html; charset=utf-8
cache-control: no-cache
expires: -1
location: /login
pragma: no-cache
set-cookie: redirect_to=; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomainsFör att demonstrera Grafana i aktion kan du hÀmta och lÀgga till . SÄ hÀr ser det ut:

Jag rekommenderar ocksÄ att lÀgga till en dashboard för node exporter: den visar noggrant vad som hÀnder med "malinerna" (CPU-belastning, minnesanvÀndning, nÀtverk, disk, etc.).
Efter detta anser jag att klustret Àr redo att ta emot och köra applikationer!
Observera angÄende byggnation
För att bygga applikationer för ARM-arkitektur finns det minst tvĂ„ alternativ. För det första kan du bygga pĂ„ en ARM-enhet. Men efter att ha tittat pĂ„ de nuvarande lasten pĂ„ tvĂ„ Raspberry Pi insĂ„g jag att de knappt klarar av byggen. DĂ€rför bestĂ€llde jag en ny Raspberry Pi 4 (den Ă€r kraftfullare och har hela 4 GB minne) â jag planerar att bygga pĂ„ den.
Det andra alternativet Àr att bygga en multi-arkitektur Docker-image pÄ en kraftfullare maskin. För det har vi . Om applikationen Àr skriven i ett kompilert sprÄk, behöver du krosskompilering för ARM. Jag kommer inte att beskriva alla instÀllningar för denna vÀg, eftersom det skulle krÀva en separat artikel. Genom att implementera detta tillvÀgagÄngssÀtt kan man uppnÄ "universella" bilder: Docker, som körs pÄ en ARM-maskin, kommer automatiskt att ladda ner den bild som passar arkitekturen.
Slutsats
Det genomförda experimentet övertrÀffade alla mina förvÀntningar: [minst] "vanilj" Kubernetes med nödvÀndig bas fungerar bra pÄ ARM, och nÀr det konfigurerades uppstod det endast nÄgra fÄ nyanser.
SjÀlva Raspberry Pi 3B+ klarar CPU-belastningen, men deras SD-kort Àr en tydlig flaskhals. Mina kollegor föreslog att det i vissa versioner finns möjlighet att starta frÄn USB, dÀr man kan koppla in ett SSD: dÄ troligtvis kommer situationen att förbÀttras.
HÀr Àr ett exempel pÄ CPU-belastningen vid installation av Grafana:

För experiment och "att prova", tycker jag att en Kubernetes-kluster pÄ "hallon" ger en mycket bÀttre kÀnsla av drift Àn till exempel Minikube, eftersom alla klusterkomponenter bÄde installeras och fungerar "pÄ riktigt".
Framtidsvisionen Àr att lÀgga till hela CI/CD-cykeln till klustret, helt genomfört pÄ Raspberry Pi. Jag skulle ocksÄ vara glad om nÄgon delar med sig av sin erfarenhet av att konfigurera K8s pÄ AWS Graviton.
P.S. Ja, "produktion" kan vara nÀrmare Àn jag trott:

P.P.S.
LÀs Àven i vÄr blogg:
- «».
KĂ€lla: habr.com
