From e6d3d0a55bd87acf992a0b5abfce348e6dddc893 Mon Sep 17 00:00:00 2001 From: pierreozoux Date: Sun, 13 Oct 2019 22:23:42 +0200 Subject: [PATCH] First commit. --- .dockerignore | 1 + .gitignore | 1 + .gitlab-ci.yml | 39 ++++++++++ .gitmodules | 3 + Dockerfile | 12 ++++ LICENSE.md | 30 ++++++++ archetypes/default.md | 6 ++ config.toml | 53 ++++++++++++++ content/Comparison/_index.md | 10 +++ content/FurtherReading/_index.md | 13 ++++ content/GettingStarted/Layers/_index.md | 62 ++++++++++++++++ content/GettingStarted/_index.md | 25 +++++++ .../BackingServices/_index.md | 38 ++++++++++ .../LibreshOperator/_index.md | 39 ++++++++++ .../UpstreamOperator/_index.md | 41 +++++++++++ content/KubernetesOperators/WrapUp/_index.md | 10 +++ content/KubernetesOperators/_index.md | 9 +++ .../ProblemToSolve/IndieHostersTale/_index.md | 14 ++++ content/ProblemToSolve/TheExample/_index.md | 24 +++++++ content/ProblemToSolve/_index.md | 17 +++++ content/WhyKubernetes/Misc/_index.md | 67 ++++++++++++++++++ content/WhyKubernetes/Operators/_index.md | 40 +++++++++++ content/WhyKubernetes/TheCloudAPI/_index.md | 55 ++++++++++++++ content/WhyKubernetes/_index.md | 20 ++++++ content/_index.md | 52 ++++++++++++++ content/contribute.md | 33 +++++++++ content/credits.md | 11 +++ content/license.md | 7 ++ content/mentoring.md | 11 +++ content/roadmap.md | 54 ++++++++++++++ k8s.yml | 57 +++++++++++++++ layouts/partials/logo.html | 1 + layouts/partials/menu-footer.html | 7 ++ public/categories/index.xml | 14 ++++ public/index.xml | 14 ++++ public/sitemap.xml | 17 +++++ public/tags/index.xml | 14 ++++ static/images/favicon.png | Bin 0 -> 15406 bytes static/images/logo.png | Bin 0 -> 4043 bytes static/images/ura.svg | 1 + static/images/webca.png | Bin 0 -> 10215 bytes themes/hugo-theme-learn | 1 + 42 files changed, 923 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .gitmodules create mode 100644 Dockerfile create mode 100644 LICENSE.md create mode 100644 archetypes/default.md create mode 100644 config.toml create mode 100644 content/Comparison/_index.md create mode 100644 content/FurtherReading/_index.md create mode 100644 content/GettingStarted/Layers/_index.md create mode 100644 content/GettingStarted/_index.md create mode 100644 content/KubernetesOperators/BackingServices/_index.md create mode 100644 content/KubernetesOperators/LibreshOperator/_index.md create mode 100644 content/KubernetesOperators/UpstreamOperator/_index.md create mode 100644 content/KubernetesOperators/WrapUp/_index.md create mode 100644 content/KubernetesOperators/_index.md create mode 100644 content/ProblemToSolve/IndieHostersTale/_index.md create mode 100644 content/ProblemToSolve/TheExample/_index.md create mode 100644 content/ProblemToSolve/_index.md create mode 100644 content/WhyKubernetes/Misc/_index.md create mode 100644 content/WhyKubernetes/Operators/_index.md create mode 100644 content/WhyKubernetes/TheCloudAPI/_index.md create mode 100644 content/WhyKubernetes/_index.md create mode 100644 content/_index.md create mode 100644 content/contribute.md create mode 100644 content/credits.md create mode 100644 content/license.md create mode 100644 content/mentoring.md create mode 100644 content/roadmap.md create mode 100644 k8s.yml create mode 100644 layouts/partials/logo.html create mode 100644 layouts/partials/menu-footer.html create mode 100644 public/categories/index.xml create mode 100644 public/index.xml create mode 100644 public/sitemap.xml create mode 100644 public/tags/index.xml create mode 100755 static/images/favicon.png create mode 100644 static/images/logo.png create mode 100644 static/images/ura.svg create mode 100644 static/images/webca.png create mode 160000 themes/hugo-theme-learn diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..191381e --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.git \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..c7c6d07 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,39 @@ +variables: + GIT_SUBMODULE_STRATEGY: recursive + +stages: + - build + - deploy + +test: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + script: + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --no-push + except: + - master + +build: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + script: + - cp $DOCKER_SECRET_CONFIG /kaniko/.docker/config.json + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination indiepaas/website:$CI_COMMIT_SHORT_SHA + only: + - master + +deploy: + stage: deploy + tags: + - deploy + image: + name: lachlanevenson/k8s-kubectl + entrypoint: [""] + script: + - cat $CI_PROJECT_DIR/k8s.yml | sed "s/latest/$CI_COMMIT_SHORT_SHA/" | kubectl apply -f - + only: + - master \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c5893f4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "hugo-theme-learn"] + path = themes/hugo-theme-learn + url = https://github.com/matcornic/hugo-theme-learn.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ac9ac02 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM unteem/nginx:1.16 +ENV HUGO_VERSION 0.58.3 +RUN apt-get update && apt-get install -y wget \ + && wget https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz \ + && tar xzf hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz \ + && rm -r hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz \ + && mv hugo /usr/bin/hugo \ + && rm -rf /var/lib/apt/lists/* +COPY . /usr/src/site +RUN cd /usr/src/site \ + && hugo -d /var/www +CMD 'nginx' diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9ebf0e7 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,30 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: + + the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + moral rights retained by the original author(s) and/or performer(s); + publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; + rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; + rights protecting the extraction, dissemination, use and reuse of data in a Work; + database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..00e77bd --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..471dfed --- /dev/null +++ b/config.toml @@ -0,0 +1,53 @@ +#baseURL = "https://kubernetes.libre.sh/" +baseURL = "https://libresh.test.indie.host/" +languageCode = "en-us" +title = "libre.sh - Kubernetes the libre way." +theme = "hugo-theme-learn" +# For search functionality +[outputs] +home = [ "HTML", "RSS", "JSON"] + +[[menu.shortcuts]] +name = " Contribute" +url = "/contribute" +weight = 10 + +[[menu.shortcuts]] +name = " Forum" +url = "https://talk.libreho.st/c/libre-sh" +weight = 11 + +[[menu.shortcuts]] +name = " Matrix Channel" +url = "https://matrix.to/#/!PqJHpgkUYykvCiwdFI:matrix.allmende.io?via=matrix.org&via=matrix.allmende.io&via=chat.weho.st" +weight = 20 + +[[menu.shortcuts]] +name = " Sponsor" +url = "https://opencollective.com/libresh" +weight = 28 + +[[menu.shortcuts]] +name = " Free mentoring" +url = "/mentoring" +weight = 25 + +[[menu.shortcuts]] +name = " Roadmap" +url = "/roadmap" +weight = 26 + +[[menu.shortcuts]] +name = " License" +url = "/License" +weight = 28 + +[[menu.shortcuts]] +name = " Credits" +url = "/credits" +weight = 30 + + +[params] + editURL = "https://lab.libreho.st/libre.sh/kubernetes/website/tree/master/content/" + themeVariant = "blue" \ No newline at end of file diff --git a/content/Comparison/_index.md b/content/Comparison/_index.md new file mode 100644 index 0000000..a6d0842 --- /dev/null +++ b/content/Comparison/_index.md @@ -0,0 +1,10 @@ +--- +title: Comparison to +pre: "5. " +weight: 5 +--- + + - yunohost + - cloudron + - freedombox + - libre.sh v1 diff --git a/content/FurtherReading/_index.md b/content/FurtherReading/_index.md new file mode 100644 index 0000000..995ac26 --- /dev/null +++ b/content/FurtherReading/_index.md @@ -0,0 +1,13 @@ +--- +title: Further reading +pre: "6. " +weight: 6 +--- + + - [The Illustrated Children's Guide to Kubernetes](https://www.youtube.com/watch?v=4ht22ReBjno) - a nice getting started + - [Julia Evan's blog](https://jvns.ca/categories/kubernetes/) - a great resource to understand internals of kubernetes + - [CoreOS cluster OSI model](https://coreos.com/blog/cluster-osi-model.html) - one of the really inspiring article that started to make me dreaming + - [Kubernetes Resource Management](https://docs.google.com/document/d/1RmHXdLhNbyOWPW_AtnnowaRfGejw-qlKQIuLKQWlwzs/edit#heading=h.sa6p0aye4ide) - a beautiful, almost academic paper - on how to run a distributed cluster that is in reallity kubernetes ;) + - [Airbnb synapse - Registration Discovery pattern](https://airbnb.io/projects/synapse/) - Discovery/Registration pattern + - [Kubernetes The hard way](https://github.com/kelseyhightower/kubernetes-the-hard-way) - a deep dive on what is needed to build a kubernetes cluster - this would help you understand the deep internals + - [Kubernetes the not so hard way with ansible](https://www.tauceti.blog/post/kubernetes-the-not-so-hard-way-with-ansible-the-basics/) - a alternative to the previous link - wiht ansible diff --git a/content/GettingStarted/Layers/_index.md b/content/GettingStarted/Layers/_index.md new file mode 100644 index 0000000..6d0675b --- /dev/null +++ b/content/GettingStarted/Layers/_index.md @@ -0,0 +1,62 @@ +--- +title: "Layers" +--- + +## Layer 0 - infra + +Create 9 machines on your favorite place. We'll not details the steps here. We use hetzner cloud. Deploy a debian or ubuntu or your favorite distrib. + +## Layer 1 - network + +Then we need to prepare the Network: + +### wireguard + +In term of network topology, we'll use 3 networks, all secured by wireguard: + + - management + - Ceph backend + - Kubernetes overlay + +Management will be used by kubernetes and ceph components. + +The ceph backend will be used for data heavy part of ceph. + +And the overlay to allow services running on top of kubernetes to discuss together. + +In this step, we'll only install The 2 first one. The overlay will be installed later. + +## Layer 2 - kubespray + +Use [kubespray](https://github.com/kubernetes-sigs/kubespray) to deploy your components. +It is a set of ansible roles to deploy an HA kubernetes cluster. + +We personnaly use the following: + + - containerd - It is the container runtime. Nowadays, the industry is moving from Docker to containerd, so do we. + - kubeadm - It ss developed Upstream Under kubernetes namespace. It is aimed at becoming the defacto installation tool for kubernetes. + - canal with wireguard - it is the Kubernetes overlay network. + - nginx-ingress + - cert-manager + +## Layer 3 - storage + +Storage with [ceph](https://codimd.indie.host/GoflbbtqT5uTgiZJOJoCKg?both) (Or [rook](https://rook.io/) if you are bold ;), it is almost production ready) + +## Layer 4 - backing services + +This term is a reference to [12 factor app](https://12factor.net/), and it is not a coincidence! (If you don't know it, get there and read it!) + +This is our curated list of operators: + + - [Postgres operator](https://github.com/zalando/postgres-operator) + - [Redis operator](https://github.com/spotahome/redis-operator) + - [Stash](https://github.com/appscode/stash/) for backups/restore + +## Layer 5 - upstream operators + +## Layer 6 - libre.sh operator + +## Layer 7 - web UI + +UI to let people self serve. The status of this is clearly [vaporware](https://en.wikipedia.org/wiki/Vaporware), but we hope to release an alpha by begining of 2021 (Except if you have skills and want to work on it already, this would be amazing ;) ). \ No newline at end of file diff --git a/content/GettingStarted/_index.md b/content/GettingStarted/_index.md new file mode 100644 index 0000000..9a5be48 --- /dev/null +++ b/content/GettingStarted/_index.md @@ -0,0 +1,25 @@ +--- +title: "Getting started" +pre: "3. " +weight: 3 +--- + +## A layered and modular architecture + +Hopefully this becomes a Quickstart ;) + +(The idea is to build [a terraform template](https://github.com/libresh/kubespray/issues/1) with kubespray to automate this part) + +This is what we'll deploy: + +The reference architecture needs 9 machines: + + - 3 masters + - 3 ingresses + - 3 compute + +Masters are used to coordinate the cluster. They store the state in etcd. They make sure nodes are healthy. They schedule work on the nodes. They are the brain of your cluster. If they are down, the three of them, your workload still functions properly, but you could't change the state of you cluster. + +Ingresses are where https is terminated. They need to also be highly available. Depending on your hardware setup, you have to find a way to balance traffic. You could use MettalLB for instance. On Hetzner VMs, we use floating IPs. + +Compute nodes are where your workload is running. This is where your databases and application servers will run. \ No newline at end of file diff --git a/content/KubernetesOperators/BackingServices/_index.md b/content/KubernetesOperators/BackingServices/_index.md new file mode 100644 index 0000000..19deb82 --- /dev/null +++ b/content/KubernetesOperators/BackingServices/_index.md @@ -0,0 +1,38 @@ +--- +title: Backing services +weight: 1 +--- + +[Backing services](https://12factor.net/backing-services) are necessary to any web application you deploy. +In our example, we'll need a prostgres database. + +So, we'll need to deploy a postgres database on kubernetes. For that we'll use an operator. There are currently [around 5 different postgres operators](https://github.com/operator-framework/awesome-operators). Libre.sh curated one for you, the one developped by [Zalando](https://github.com/zalando/postgres-operator). + +Once you have [the operator running](https://github.com/zalando/postgres-operator/blob/master/docs/quickstart.md) in your cluster, we can deploy a postgres instance. + +With the kubernetes API that is declarative, this is how you'd do: + +``` +cat << EOF | kubectl apply -f - +apiVersion: "acid.zalan.do/v1" +kind: postgresql +metadata: + name: nexctloud-postgres + namespace: fight-marketing +spec: + teamId: "nextcloud" + volume: + size: 1Gi + numberOfInstances: 2 + users: + nextcloud: # database owner + - superuser + - createdb + databases: + nextcloud: nextcloud # dbname: owner + postgresql: + version: "11" +EOF +``` + +After some minutes, you get a highly available Postgres cluster running. Nice right? Now let's deploy Nextcloud. \ No newline at end of file diff --git a/content/KubernetesOperators/LibreshOperator/_index.md b/content/KubernetesOperators/LibreshOperator/_index.md new file mode 100644 index 0000000..6750b54 --- /dev/null +++ b/content/KubernetesOperators/LibreshOperator/_index.md @@ -0,0 +1,39 @@ +--- +title: Libre.sh operator +weight: 20 +--- + +As explained at the begining, libre.sh is a distribution where services are curated and integrated together. +We assume that you use the zalando postgres operator. We also know the domain name from the annotation in the namespace. + +So then we can build a libre.sh Nextcloud operator (still to be built). + +And deploying a Nextcloud instance would look like this: + +``` +cat << EOF | kubectl -f - +apiVersion: "nextcloud.libre.sh/v1" +kind: cloud +metadata: + name: fight-marketing + namespace: fight-marketing +EOF +``` + +Or put differently: + +``` +curl https://yourkubernetes.cluster/nextcloud.libre.sh/v1/cloud/ -k -H "Content-Type: application/json" -XPOST -d ' +{ + "apiVersion": "nextcloud.libre.sh/v1", + "kind": "cloud", + "metadata": { + "name": "fight-marketing", + "namespace": "fight-marketing", + } +}' +``` + +And yes, you can use an OpenIdConnect provider with RuleBasedAccessControl in front of the kubernetes api if you see where we are going ;) + +You could also imagine adding a spec, like `size: L`, and it would translate into a price and cpu and memory request for the different components. \ No newline at end of file diff --git a/content/KubernetesOperators/UpstreamOperator/_index.md b/content/KubernetesOperators/UpstreamOperator/_index.md new file mode 100644 index 0000000..f019759 --- /dev/null +++ b/content/KubernetesOperators/UpstreamOperator/_index.md @@ -0,0 +1,41 @@ +--- +title: Upstream operator +weight: 10 +--- + +Now that Postgres is running, we can deploy our Nextcloud. + +But first we need to deploy the nextcloud upstream operator. This operator is low level because it would need every information about the backing services. It is a nice building block for projects like libre.sh. + +Here is the version alpha we are developing. The goal is to move it's development upstream. We think that this belongs to Nextcloud community to own this. And we'll help bootstrap that. The same way we did with [some](https://github.com/RocketChat/Docker.Official.Image/commit/a951f488fb2a633fc89ad3048eb451aa05dc90ee) [official](https://github.com/nextcloud/docker/commit/8fa384bcd6619b9c19c5efbcdf7248d803e43727) [docker](https://github.com/matomo-org/docker/commit/e6538b90a4c7e7e3d6423d1e4740e674ee42eede) [images](https://github.com/idno/Known-Docker/commit/394e91c21d33914899dd2b0b211be2d7fe4e1837). + +Here is how the Nextcloud instance object would look like: + +``` +cat << EOF | kubectl apply -f - +apiVersion: "nextcloud.com/v1" +kind: nextcloud +metadata: + name: cloud + namespace: fight-marketing +spec: + postgress: + endpoint: nextcloud-postgres + secret: nextcloud-postgres-secret + volume: + size: 1Gi + numberOfInstances: 2 + domainNames: + - fight.marketing +EOF +``` + +After some minutes, you'd get an up and running Nextcloud instance. behind the scene it would have provisionned the following: + + - the deployment with 2 pods with a php container with Nextcloud code + - a cron job + - a web container to serve static assets + - an ingress with a Let's Encrypt certificate + - installed Nextcloud + +Upstream operators are already nice you'd say. But keep in mind that we are discussing about 7 backing services. So for each Nextcloud instance, you'd need to do the plumbing manually of each backing service manually. Let's go now to the libre.sh operator. \ No newline at end of file diff --git a/content/KubernetesOperators/WrapUp/_index.md b/content/KubernetesOperators/WrapUp/_index.md new file mode 100644 index 0000000..332d0e4 --- /dev/null +++ b/content/KubernetesOperators/WrapUp/_index.md @@ -0,0 +1,10 @@ +--- +title: Wrap Up +weight: 100 +--- + +This concept of upstream and libre.sh operators enables us to build nice and reusable code upstream and also have a really tailored operator for our libre.sh needs. + +Once the 2 levels are built, it is not difficult to add a UI on top of it. + +You might also be frustrated by this need of 2 level operators, there must be a better solution for it. We do think so too, but it is a [long term goal that we have to work with the kubernetes community](https://github.com/kubernetes/enhancements/issues/706). \ No newline at end of file diff --git a/content/KubernetesOperators/_index.md b/content/KubernetesOperators/_index.md new file mode 100644 index 0000000..b11a282 --- /dev/null +++ b/content/KubernetesOperators/_index.md @@ -0,0 +1,9 @@ +--- +title: Kubernetes Operators +pre: "2. " +weight: 2 +--- + +For a more in depth view of what is kubernetes or what are opertators, you should read the [why kubernetes](/whykubernetes/). + +But first let's move on with our example to start to get a taste of what is our solution. \ No newline at end of file diff --git a/content/ProblemToSolve/IndieHostersTale/_index.md b/content/ProblemToSolve/IndieHostersTale/_index.md new file mode 100644 index 0000000..6dc9985 --- /dev/null +++ b/content/ProblemToSolve/IndieHostersTale/_index.md @@ -0,0 +1,14 @@ +--- +title: IndieHosters tale +weight: 1 +--- + +## Once upon a time in Lisboa + +a young guy started to host WordPresses for his friends on a Raspberry. Then he teamed with a new friend to create IndieHosters. This project was about hosting free software for people. Little by little, they gained popularity and bigger and bigger organisations trusted them to host there software. There was some up and some down about how to pay the rent with this service. A new team member came along. And eventually, they finally managed to secure a bigger client and some nice smaller one. The rent was mostly paid. + +At this point, they realized that hosting for people is just not profitable at all, or they would need some volume. + +If they were capitalistic and rational, IndieHosters would have stop hosting for the people and focus on the bigger clients. Some people say that startup start B2C and end up doing B2B, it is just plain easier. But IndieHosters was not a startup and the tale didn't end up like this. + +We think that this tale is what most small free software hosters are experiencing. We want to change this, and here it is how: \ No newline at end of file diff --git a/content/ProblemToSolve/TheExample/_index.md b/content/ProblemToSolve/TheExample/_index.md new file mode 100644 index 0000000..f479f17 --- /dev/null +++ b/content/ProblemToSolve/TheExample/_index.md @@ -0,0 +1,24 @@ +--- +title: Nextcloud example +weight: 3 +--- + +Heard about [Nextcloud](https://nextcloud.com/)? Let's say that we want to deploy a Nextcloud instance for a nice association fighting marketing. + +Before deploying the Nextlcoud, we'll need to list the backing services that would be necessary: + + - postgres database + - redis cache + - smtp relay to send email notification + - S3 compatible API for object store + - OpenIdConnect provider for Single Sign On + - libre office online + - nextant + +This is to deploy a reliable and scalable Nextcloud instance. For the sake of simplicity, let's say that we just need Postgres. But keep in mind, that we have the other backing services in mind too, and that we can use the same paradigms. + +We'll assume for the rest of the example that you already got a kubernetes cluster running. + +The domain name of the association is `fight.marketing`. You already created a namespace `fight-marketing` and added the annotation `domaine-name: fight.marketing`. + +Let's now see how we solve our problem with the [kubernetes operators](/kubernetesoperators/). \ No newline at end of file diff --git a/content/ProblemToSolve/_index.md b/content/ProblemToSolve/_index.md new file mode 100644 index 0000000..da655ee --- /dev/null +++ b/content/ProblemToSolve/_index.md @@ -0,0 +1,17 @@ +--- +title: Problem to solve +pre: "1. " +weight: 1 +--- + +What is the problem to solve? It is always a great question to ask before starting a project. + +For libre.sh, since version 1, the problem to solve is the same, How do we : + + - host free software at scale? + - make it cheaper in term of admin time? + - improve reliability of our platform? + - share our SysAdmin recipes? + + These are the questions we think we found answers in 2 words, [kubernetes](/whykubernetes/) operators. + Let's detail what we envision about operators and why it is so great. But first, let's start with a little tale. \ No newline at end of file diff --git a/content/WhyKubernetes/Misc/_index.md b/content/WhyKubernetes/Misc/_index.md new file mode 100644 index 0000000..2c69517 --- /dev/null +++ b/content/WhyKubernetes/Misc/_index.md @@ -0,0 +1,67 @@ +--- +title: Misc +weight: 2 +--- + +## strenghts of kubernetes + +Industry is moving to kubernetes, so we benefit from the best engineers developing the most amazing platform. We are living the same revolution as when industry moved from hardware to VM. Now we are moving from VMs to containers. + +The platform is highly available by design and also highly scalable. It can also run on a single host if you want. + +## the green argument + +Another argument for using an orchestration platform like kubernetes is resource consumption. +Once you run on a datacenter with green energy and use second hand hardware, what can you do to move further? You have to use more each cpu. + +Compared to classic virtualisation, you can put a lot more services with kubernetes. A VM gets a cpu and memory allocated whereas containers can grow dynamically (you can also reserve resources for critical workload). + +And because of this reason, kubernetes is greener than classic virtualisation. + +## complexity + +Kubernetes is made to [manage thousands of VMs and hundred of thousands of pods](https://docs.openshift.com/container-platform/4.2/scalability_and_performance/planning-your-environment-according-to-object-limits.html). At this scale, the underlying platform has to be somewhat complex. + +But once you understand kubernetes, you realize that it is actually beautifully simple inside. + +Everythings relies on the registration discovery pattern. Here is how to schedule a pod on a node for instance: + + - from cli, you execute `kubectl run --image=nginx nginx-app` to start an nginx pod + - your cli goes to the API to make this POST request + - the API registers in the central database that a pod has to run with this image + - the scheduler discovers that a pod has to run and doesn't run yet + - the scheduler finds an appropirate node to run the pod and registers it in the database + - the node discovers that it has to run a pod and doesn't run yet + - the node starts the pod and write back the status of the pod in the database + - from your cli, you get the status of the pod + +Go to [Julia Evan's blog](https://jvns.ca/categories/kubernetes/) to discover more, it is amazing content ;) + +As you see, it is pretty simple, and that's why it is reliable, even at the google scale. + +In term of network, it is also complex, as you have to span an overlay network between different host and give an IP address to each pod. It is a complex problem to solve, but some smarter people already solved it, so we can rely on such solutions. + +## security + +Security depends a lot on your threat modeling. + +It is a fact that containers are less secured than VMs. But then it depends on what kind of isolation you need. + +If you want to run free software for people, we think that containers are more than secure enough. We know which code runs on our hardware, and we don't think we need VM level isolation. And if there is a bug in linux containers, we patch. + +## single host + +Kubernetes could also run on a single host. Some people might think it would be overkill to run so many processes, for just running one applciation, but why not? + +There is an effort called [k3s](https://github.com/rancher/k3s), and it is said to run on 150MB of RAM. Now imagine that you can shutdown the control plane, and run it with a cron once a night to update. It could make a good candidate to evolve https://lollipopcloud.solutions/ or even yunohost. + +## declarative API + +The kubernetes API is declarative. It means you declare how the world should look like. +For instance, you can say, "My desire is to have a redis instance with these parameters". + +This is diffrent than a imperative API. For our redis instance, it would mean to say instead: "Please create a redis instance, then create a service to expose it, and finally, create a secret and configure redis with that." + +In the declarative case, there is no need to detail the flow that modifies the different states. + +This is what allows us to build higher level objects, like Nextcloud instances, and hide all the logic in our operators, and make the end user desire happen. diff --git a/content/WhyKubernetes/Operators/_index.md b/content/WhyKubernetes/Operators/_index.md new file mode 100644 index 0000000..9bc490b --- /dev/null +++ b/content/WhyKubernetes/Operators/_index.md @@ -0,0 +1,40 @@ +--- +title: Operators +weight: 3 +--- + +As we saw in the previous paragraph, kubernetes is now the standard cloud API. But kubernetes, the open source upstream project is really narrowed. +It doesn't want to make everything, and want to stay focus on the good foundations. + +#### Resources + +A popular analogy is that kubernetes is providing the lego bricks so that you can build the Pirate ship yourself. These bricks are the resources. + +At the beginning, when you deploy kubernetes, you get these lego bricks, the resources that make a cloud API: + + - pods (compute and memory) - they are composed of containers + - service (L4 load balancer) + - ... + +One way to extend the kubernetes API, is with what is called Custom Resource Definition (CRD). + +#### History + +The concept of operator was first introduced by [CoreOS in this blog post](https://coreos.com/blog/introducing-operators.html), and we recommend you to read it. We got immediately excited, and now all the industry is speaking about it. There is even a WordPress operator (More on that later). + +#### Why is it so exciting + +Operators are a way to write as code the lifecycle of an hosted app, in our case a free software hosted app. +It is probably the first time in IT history that we can collaborate about how to: + + - install + - delete + - update + - backup + - restore + - scale + - run highly available + +These recipes can now be code. It means they can be tested and shared with a Freesoftware license. + +The combination of kubernetes and operators on not is really appealing as a platform to run free software hosted apps. diff --git a/content/WhyKubernetes/TheCloudAPI/_index.md b/content/WhyKubernetes/TheCloudAPI/_index.md new file mode 100644 index 0000000..d668cad --- /dev/null +++ b/content/WhyKubernetes/TheCloudAPI/_index.md @@ -0,0 +1,55 @@ +--- +title: The cloud API +weight: 1 +--- + +The main argument is favor of kubernetes is probably the fact that this is becoming The cloud API. + +## A bit of history + +In 2006, Google contributed the code to the linux kernel to make linux containers possible. Then heroku started their PaaS business. And RedHat started their Open Source PaaS - OpenShift - to stay relevant in the business. +Fast forward to 2015, RedHat joins Google to found the Cloud Native Computing Foundation and work together on Kubernetes, under the umbrella of the Linux FOundation. + +Between the lines, you can read that the intent behind kubernetes is to become, at least, a really good cloud API. + +## Not the first abstraction to cloud APIs + +It is not the first time that open source project try to be an abstraction to popular cloud vendors. There are a few like ansible, or terraform. But they failed, because at the end of the day, you need to take care of the little vairations of each provider. + +## what is a cloud API + +But what is a cloud API anyway you can ask. It is a way to provision: + + - compute (CPU) + - memory + - disk + - network (L4 and L7) + +With an API, in a self service fashion. + +The difference between terraform and kubernetes is the way this abstraction is made. Take disk for instance. +In kubernetes, they are called Persistent Volumes (PV), and when you work with kubernetes, you manipulate thes objects. +Then, depending on your cloud provider (Google Cloud, AWS, ..) or even behing in your own datacenter, you can have a different volume provider taking care of making your desire to have a Volume happen. +Even better, you could have different volume providers on the same cluster, all nicely abstracted by this object. + +And kubernetes provides this nice abstraction for everyhting you need to run hosted free software, in a beautiful way. + +## Google compete against AWS + +Another thing to keep in mind is that Google Cloud is a direct competitor of AWS. +At the time of open sourcing Kubernetes, the docker orchestration war already started, and the world was desparetly in need of a nice orchestrator. Google had a bit of experience in this field. And They saw a nice opportunity to compete against AWS. + +Imagine, if the world adopts Kubernetes, which is what is happening. Then the barrier to exit AWS just became a lot cheaper. +It is not a secret that AWS was one of the last big tech compagny to join the CNCF. And it is probably because, kubernetes is a threat to their business model, to some extent. + +## The last package manager? + +There is this nice read from Helm about [what is a package manager](https://github.com/helm/community/blob/master/helm-v3/009-package_manager.md). And if you think like [CoreOS that the future of the datacenter, or the cloud, is to build an Operating System](https://coreos.com/blog/cluster-osi-model.html). (Funnily this is also Mesos - another container orchestrator - Marketting [DataCenter OS](https://dcos.io/)). + +Whether you agree with what was said before, it has at least the merit to ask questions. How do we run cluster at scale? How do we deploy in a high availability manner? How do we backup and restore? And more importantly, how do we share these recipes, with a free software license attached. + +WordPress, the code is free software. Great you can install it on your php provider. But then the exercise of installing and updating is left as an exercise to the reader. + +One component of Kubernetes is definitely to address that. To some extent, kubernetes, and/or tools around are becoming the standard package manager. Some popular proprietary vendors like SAP are now shipping their software as a kuberntes package. They tell their customers, just provide us a cluster, we take care of the rest. Even [OpenStack](https://github.com/openstack/openstack-helm) is shipped as a kubernetes package! + +For all these reasons, kubernetes is becoming The cloud API, and the OS of your infrastructure, and the package manager to deploy your FLOSS. \ No newline at end of file diff --git a/content/WhyKubernetes/_index.md b/content/WhyKubernetes/_index.md new file mode 100644 index 0000000..42e60e9 --- /dev/null +++ b/content/WhyKubernetes/_index.md @@ -0,0 +1,20 @@ +--- +title: "Why Kubernetes" +pre: "4. " +weight: 4 +--- + +We have an apriori that the free software community doesn't really like kubernetes, so this section is about why it is a such a great platform for hosting free software. Maybe it is just because this community doesn't really know it, or maybe we are just plain wrong. + +The only fact that this technology is trendy doesn't make it the best choice. But the fact that everybody is talking about it +should at least question your beliefs. Some people complain about it, and maybe you do too: + + - containers are not secured + - kubernetes is developed by google + - it is really complex + - it consumes too much resources + - the network stack is crazy + +Let's try to address these, and try to convince you that kubernetes is a good platform to host free software. +(If you still have some unanswered questions, feel free to come and get in touch) + diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..5e8b7f0 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,52 @@ +# Libre.sh - Kubernetes the libre way. + +## Introduction + +The current status of the project is pre-alpha. It is mostly documentation but should already be helpful. The plan is to automatize more parts. + +Libre.sh is a kubernetes distribution aimed at hosting freesoftware for the people. Think of it as the debian of kubernetes distribution for hosted software, or as a distributed yunohost. + + - Currently contains documentation - From Hetzner to Nextcloud + - No single point of faillure + - Scallable + - High density of services + - Everything as code + +## How it works + +It is an opinionated but modular kubernetes distribution. We don't have a strong opinion about lower layers, but we want to collaborate on top. The aim of this distribution is to host free software for people and organisation at scale. + +Like for libre.sh v1 - yet another docker-compose PaaS, we use the best FLOSS tools out there, put them together, add a bit of configuration, make upstream compatible and enjoy the ride. + +In our case, once you got kubernetes working, the rest is based on operators. + +We have one opinion on how to organize the cluster, is that, we namespace it by domain name, and we put this domain as an annotation, it allows us for some automation later. + +Then we curate a list of nice operators for the backing services (Think s3, postgres, redis, emails..). + +We also need to build/advocate for upstream operators for popular FLOSS projects. We already helped develop [some](https://github.com/RocketChat/Docker.Official.Image/commit/a951f488fb2a633fc89ad3048eb451aa05dc90ee) [official](https://github.com/nextcloud/docker/commit/8fa384bcd6619b9c19c5efbcdf7248d803e43727) [docker](https://github.com/matomo-org/docker/commit/e6538b90a4c7e7e3d6423d1e4740e674ee42eede) [images](https://github.com/idno/Known-Docker/commit/394e91c21d33914899dd2b0b211be2d7fe4e1837) on the v1, we now want to help develop this operators. + +Then we wrap these operators as libre.sh operators. These are the upstream operators, packaged with the necessary dependencies to make them work out of the box in libre.sh. + +Finally, we want to build a nice UI to let end user self serve these great FLOSS tools. The objective would be to have an alpha version of this UI by beginning of 2021. + +This is the general idea of the libre.sh kubernetes distribution. This is early and work in progress, feel free to get in touch to discuss about the different points. + +## Who uses it + +It is currently used and developed at https://indie.host in production since more than a year. +If you use it or plan to use it, feel free to add your organization here! + +## Who is it for + +If you need to deploy just a couple of Nextcloud instances, then this is probably not for you. + +If you want to selfhost on a single host, it is probably not mature enough for you to have a seemless experience (but hopefully we'll get there). + +If you want to discover kubernetes, and you are a free software enthousiast, you are at the right place. + +If you need to build an infrastructure to host free software that would need to scale, it is also for you. + +If you need high availability and you are not allergic to containers, even for one Nextcloud instance, it could be for you. + +If you need to host hundreds of free software instances, like we do, it is definitely for you! diff --git a/content/contribute.md b/content/contribute.md new file mode 100644 index 0000000..df7d0fc --- /dev/null +++ b/content/contribute.md @@ -0,0 +1,33 @@ +--- +title: How to contribute +--- + +Depending on your skills, there are many ways to contribute: + + - devops + - improve docker images upstream + - build pipelines to test them and ship them + - build pipelines for our operators + - go developper + - develop operators upstream + - develop libre.sh operators + - make kubernetes ecosystem thrive + - UI/UX designer/developer + - help us shape what the UI will be like --> https://lab.libreho.st/libre.sh/kubernetes/ui + - translate + - not much yet, once we have the UI, it would make sense to translate it + - documentation + - improve this website + - spread the word about this tool + - write blog post + - tweet and toot about it + - discuss on reddit + - discuss with your community + - support + - support people on the forum + - support people over the matrix channel + - learn + - kubernetes + - go + - donate :) + - https://opencollective.com/libresh \ No newline at end of file diff --git a/content/credits.md b/content/credits.md new file mode 100644 index 0000000..3735810 --- /dev/null +++ b/content/credits.md @@ -0,0 +1,11 @@ +We'd like to thanks the following that make building and hosting this website possible: + + - Elio Quoshi and https://ura.design for the beautiful logo they designed us. + - https://github.com/matcornic/hugo-theme-learn + - https://getgrav.org + - https://gohugo.io/ + - https://gitlab.com + - https://Kubernetes.io + - https://github.com/GoogleContainerTools/kaniko + - https://hub.docker.com + - https://indie.host \ No newline at end of file diff --git a/content/license.md b/content/license.md new file mode 100644 index 0000000..e9526c6 --- /dev/null +++ b/content/license.md @@ -0,0 +1,7 @@ +--- +title: License +--- + +In case you wondered, it is a free software project, not open source. This is a project about human rights, privacy rights, freedom of speech (in the european sense), not about efficient development. This wesbite is public domain and we use AGPL, [your compagny problably doesn't like](https://opensource.google/docs/using/agpl-policy/), and not MIT. We consider that the greatest freedom, shouldn't be a company freedom to be able to close the source code, but a user freedom to always have it free and libre. + +If you are aligned with these values, you are welcome to contribute. If you feel uncomfy, we are sorry, but this is not negociatable. \ No newline at end of file diff --git a/content/mentoring.md b/content/mentoring.md new file mode 100644 index 0000000..ea02cec --- /dev/null +++ b/content/mentoring.md @@ -0,0 +1,11 @@ +--- +title: Free mentoring +--- + +We believe in this project, and want to make it successful. + +If you are a free software enthousiast and would like to contribute to this project, in any way, feel free to reach us --> contact at indie dot host. + +We want to offer free mentoring to help you getting started with this project. + +We don't know yet which form it will take, but we'll learn it together. \ No newline at end of file diff --git a/content/roadmap.md b/content/roadmap.md new file mode 100644 index 0000000..1e3f4fb --- /dev/null +++ b/content/roadmap.md @@ -0,0 +1,54 @@ +## Release management + +Let's first define what we call alpha, beta, and stable. + +### Alhpa + +Alpha is just a proof of concept. It works on my machine, and it is freshly shipped. +Use it at your own risk. + +### Beta + + - It is deployed in production for some bold people + - There is some rough documentation + - Most bugs are corrected + - There is an upgrade path + - There are backups and restore + +### Stable + + - It is observed + - grafana dashboard + - prometheus metrics + - prometheus alerts + - The documentation is nice + - it is deployed in production for more than a dozen of people + +## Roadmap + + - 1st of February libre.sh full stack alpha + - terraform for hetzner cloud + - nextcloud libre.sh operator + - editoria libre.sh operator + - proposed release name - Louise Michel + - 1st of May + - graduate nextcloud libre.sh operator to Beta + - graduate editoria libre.sh operator to Beta + - graduate the documentation of libre.sh to Beta + - Have some user stories written for the UI + - Find a decision for the UI - is it called libre.sh UI or is it called sook? + - rocketchat libre.sh operator alpha + - discourse libre.sh operator alpha + - codimd libre.sh operator alpha + - proposed release name - Proudhon + - 1st of August + - graduate nextcloud libre.sh operator to stable + - graduate editoria libre.sh operator to stable + - graduate the documentation of libre.sh to stable + - graduate rocketchat libre.sh operator to beta + - graduate discourse libre.sh operator to beta + - graduate codimd libre.sh operator to beta + - have some wireframe mockups for the UI + - proposed release name - Voltairine de Cleyre + - 1st of November + - proposed release name - Bakunin (you get the pattern ;) ) \ No newline at end of file diff --git a/k8s.yml b/k8s.yml new file mode 100644 index 0000000..2cc79f2 --- /dev/null +++ b/k8s.yml @@ -0,0 +1,57 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: libresh +spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - env: + image: indiepaas/website:latest + imagePullPolicy: Always + name: libresh + ports: + - containerPort: 80 +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + kubernetes.io/tls-acme: "true" + certmanager.k8s.io/cluster-issuer: letsencrypt-prod + name: libre-sh + namespace: libre-sh +spec: + rules: + - host: libresh.test.indie.host + http: + paths: + - backend: + serviceName: libresh-web + servicePort: http + path: / + tls: + - hosts: + - libresh.test.indie.host + secretName: libresh-tls +--- +apiVersion: v1 +kind: Service +metadata: + name: libresh-web + namespace: libre-sh +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 80 + selector: + app: nginx \ No newline at end of file diff --git a/layouts/partials/logo.html b/layouts/partials/logo.html new file mode 100644 index 0000000..6509cf7 --- /dev/null +++ b/layouts/partials/logo.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/layouts/partials/menu-footer.html b/layouts/partials/menu-footer.html new file mode 100644 index 0000000..df1f103 --- /dev/null +++ b/layouts/partials/menu-footer.html @@ -0,0 +1,7 @@ +

Sponsors

+ + +Ura is a digital studio which focuses on visual communication solutions tailored for Open Source and Internet Freedom projects. + +

+

Built with from Grav and Hugo

\ No newline at end of file diff --git a/public/categories/index.xml b/public/categories/index.xml new file mode 100644 index 0000000..6e385a4 --- /dev/null +++ b/public/categories/index.xml @@ -0,0 +1,14 @@ + + + + Categories on My New Hugo Site + http://example.org/categories/ + Recent content in Categories on My New Hugo Site + Hugo -- gohugo.io + en-us + + + + + + \ No newline at end of file diff --git a/public/index.xml b/public/index.xml new file mode 100644 index 0000000..884a3c9 --- /dev/null +++ b/public/index.xml @@ -0,0 +1,14 @@ + + + + My New Hugo Site + http://example.org/ + Recent content on My New Hugo Site + Hugo -- gohugo.io + en-us + + + + + + \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..8d64bb4 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,17 @@ + + + + + http://example.org/categories/ + + + + http://example.org/ + + + + http://example.org/tags/ + + + \ No newline at end of file diff --git a/public/tags/index.xml b/public/tags/index.xml new file mode 100644 index 0000000..a8ab33c --- /dev/null +++ b/public/tags/index.xml @@ -0,0 +1,14 @@ + + + + Tags on My New Hugo Site + http://example.org/tags/ + Recent content in Tags on My New Hugo Site + Hugo -- gohugo.io + en-us + + + + + + \ No newline at end of file diff --git a/static/images/favicon.png b/static/images/favicon.png new file mode 100755 index 0000000000000000000000000000000000000000..ecadbe1bd68444f0ce081a0dd2f2e7e2197dfd88 GIT binary patch literal 15406 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

Z_8udhVdMCp;qbO%n7GjaH&ti2@$} zf{XJmLaY?*EgZs&B>O!A1GhYB;N9z0a*-KK83_tIS1Ysubl$G;iPB=)8j2j0w(LZwTe)j1wdSm&@ zYbhp}R81OrkHG7V*pR`VN1qG10{YZoFw_Cx+~hgrHRlSFA8uXpA2Xw8ysbm_S2>H6 zDP&C7ls~BFBw391zFFQg_+s>j|9N_F!rS6n^i^lGvcR57uezbumrWNV#Kjx*nS9+n zkX|o8)ASUjcj21PxJCN*@?X+*)$pT3zb?`Lh?|Z6f%)=(*TO$fKi&KlxB;B7F{sXr6gxNgxCq@T;L(T{9O|FR#?JU@a!4=R>>+O)g z&yJn-XCB}|H)p7H1|YRHL7sJ-YbA#wkf$i@7fxkbrEDme7IWs=QsWSjn!V{3IBy$73moUm-xSg z?uU^t<%uErT>9*?yLOI4x=W8xXAW}Nl<`k~dN=&imE}2TUGh1SWAg)MgO2&LVdSKH zFWCU22eO(+uM~6ix4vpQl;ObMVQT5I#b$%Oz`27!|AMsyi_T%H-XHiXA4_`ssdy|( z-(EUx=OLh#YUTm_-e(e5=T(1s*K#uB12{C}k4L(HiC^5mo~|{l24H^G#{kMPtVe`A zTg*(aeU<#h;s))FzFg~ni};uh`b6rB%iosZF429I^NJblgY~&ImwPY$#s3^WN@s5@ z-UW8`AeI+6f1o}a?=^4lCHT$%n;v*w5PetZpDW%B=%5~#VxJ(!5&eQo`lC3xoqm+; zzvjsA2=~Db&K+6L3E#~)vzwaBcNkzx2!=d1DT#k~R3 z8e?`NyGf7nIkTHSKR<@;B;Uez2nXRDTv}Uxp^Y3(uhuWOwFtj->GvO%8_)&B?Y{KU-cEMX~C22#Ecss`M`XC7J1Nf zuXm-m6*`-~m1*S9WDU0c7}$MZT{gRoGrU>z|DxknKJNnUroJjJk?zlqSDHN+eJqXv z2lYs;2acRsoB^yxZ+`3ixb$ItWsoysvv!VCwnwxS{9Zn(KT;nZ;0(LY8yoGRyUwJq zaSlMaukrUfI&(7!XfLfJdpb7ouIB-A)O3dW+1caB%hUA#yG-{2ok=97ILrK^Yppry znu%eRPYrbbJNBD1V+NCCEk&Qa)-~f>hQQ9Jl&i@~;|)*$=Z7_)}N;<)s~g3jrP4|wpX;wP`_)v{8X9diiKLK-*%IiDMt5t>S?rl zpIfE*&CZj}m+j1E)~W4wxQTwwWa>;9vZ3>K8_0S#7pI;i zixv+-bBl+d)9t*x-|~Aao|cX3Z~FFXkCH(ENRRWR=v>)(=fDgMAO{Q?Sc7?GShG@F^L|rMFx* z6*ph3GiQpAcgL)UwfAAHqxjM1B*3n7qo&*5_F8TvA992E^Dtnzv&QYG0LO#wTz4pN H%M|!u^1+X+ literal 0 HcmV?d00001 diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..89dc6ada61273e898c116ca5ed959d8cacf8ea28 GIT binary patch literal 4043 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 literal 0 HcmV?d00001 diff --git a/static/images/ura.svg b/static/images/ura.svg new file mode 100644 index 0000000..a1f3f90 --- /dev/null +++ b/static/images/ura.svg @@ -0,0 +1 @@ +logo \ No newline at end of file diff --git a/static/images/webca.png b/static/images/webca.png new file mode 100644 index 0000000000000000000000000000000000000000..52773f3aa026fe0e3114d9a76766176c18419f50 GIT binary patch literal 10215 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 Ms_Lp#DOapSpWb4 literal 0 HcmV?d00001 diff --git a/themes/hugo-theme-learn b/themes/hugo-theme-learn new file mode 160000 index 0000000..51dbdcf --- /dev/null +++ b/themes/hugo-theme-learn @@ -0,0 +1 @@ +Subproject commit 51dbdcf4aaef01d02e78a6ea76b2a6fde5842b55 -- GitLab