-
Notifications
You must be signed in to change notification settings - Fork 0
/
704d5351.10cb3168.js
1 lines (1 loc) · 11.5 KB
/
704d5351.10cb3168.js
1
(window.webpackJsonp=window.webpackJsonp||[]).push([[19],{153:function(e,t,n){"use strict";n.r(t),n.d(t,"frontMatter",(function(){return l})),n.d(t,"metadata",(function(){return s})),n.d(t,"rightToc",(function(){return i})),n.d(t,"default",(function(){return c}));var a=n(2),r=n(9),o=(n(0),n(173)),l={id:"16-7-20",title:"Automated HA Kubernetes deployment on Raspberry Pis",author:"Michael Fornaro",author_title:"Senior DevOps Engineer",author_url:"https://github.com/xUnholy",author_image_url:"https://avatars3.githubusercontent.com/u/20387402?s=400&u=fbb33b14f7f7328a98ea87dc162a334c9bc97523&v=4",tags:["kubernetes","raspberry pi","raspbernetes"]},s={permalink:"/blog/16-7-20",editUrl:"https://github.com/raspbernetes/docs/edit/master/website/blog/blog/16-7-20.md",source:"@site/blog/16-7-20.md",description:"Prerequisites",date:"2021-02-08T21:41:27.523Z",tags:[{label:"kubernetes",permalink:"/blog/tags/kubernetes"},{label:"raspberry pi",permalink:"/blog/tags/raspberry-pi"},{label:"raspbernetes",permalink:"/blog/tags/raspbernetes"}],title:"Automated HA Kubernetes deployment on Raspberry Pis",readingTime:3.565,truncated:!0,nextItem:{title:"Istio Control Plane Upgrades using Canary Deployments",permalink:"/blog/5-8-20"}},i=[{value:"Prerequisites",id:"prerequisites",children:[]},{value:"Flash SD Cards",id:"flash-sd-cards",children:[]},{value:"Cluster Configuration",id:"cluster-configuration",children:[{value:"Basic Setup",id:"basic-setup",children:[]},{value:"Advanced Setup",id:"advanced-setup",children:[]}]},{value:"Cleanup",id:"cleanup",children:[]},{value:"Summary",id:"summary",children:[]}],u={rightToc:i};function c(e){var t=e.components,n=Object(r.a)(e,["components"]);return Object(o.b)("wrapper",Object(a.a)({},u,n,{components:t,mdxType:"MDXLayout"}),Object(o.b)("h2",{id:"prerequisites"},"Prerequisites"),Object(o.b)("p",null,Object(o.b)("strong",{parentName:"p"},"Hardware")," \u2014 It\u2019s recommended to have at least 4 raspberry pis as a minimum. Through this guide 3 will be used as master nodes, and 1 will be used as a worker node, however, having more is completely fine."),Object(o.b)("p",null,Object(o.b)("strong",{parentName:"p"},"Software")," \u2014 You will need the following CLI tools to be able to follow the steps in this guide:"),Object(o.b)("ul",null,Object(o.b)("li",{parentName:"ul"},"Ansible"),Object(o.b)("li",{parentName:"ul"},"Flash"),Object(o.b)("li",{parentName:"ul"},"kubectl")),Object(o.b)("p",null,"Finally, you will need to clone this repository: ",Object(o.b)("a",Object(a.a)({parentName:"p"},{href:"https://github.com/raspbernetes/k8s-cluster-installation"}),"https://github.com/raspbernetes/k8s-cluster-installation")),Object(o.b)("h2",{id:"flash-sd-cards"},"Flash SD Cards"),Object(o.b)("p",null,"To configure each node with a unique IP and hostname we use cloud-init. This is a method for cross-platform cloud instance initialization which also works for bare-metal installations."),Object(o.b)("p",null,"The operating system used in this guide will be Ubuntu 20.04, run the following command to download the image:"),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),'# Download the Ubuntu 20.04 Focal image for Raspberry Pis\ncurl -L "http://cdimage.ubuntu.com/releases/focal/release/ubuntu-20.04.1-preinstalled-server-arm64+raspi.img.xz" -o ~/Downloads/ubuntu-20.04.1-preinstalled-server-arm64+raspi.img.xz\n# Extract the downloaded files\nunxz -T 0 ~/Downloads/ubuntu-20.04.1-preinstalled-server-arm64+raspi.img.xz\n')),Object(o.b)("p",null,"The following steps will configure networking for the nodes automatically using cloud-init on boot( steps 4 to 6 must be repeated for each node):"),Object(o.b)("ol",null,Object(o.b)("li",{parentName:"ol"},Object(o.b)("p",{parentName:"li"},"Open the cloud-config file.")),Object(o.b)("li",{parentName:"ol"},Object(o.b)("p",{parentName:"li"},"Update the gateway4 value to match the IP of your router. (If unsure you can find this IP using this guide)")),Object(o.b)("li",{parentName:"ol"},Object(o.b)("p",{parentName:"li"},"Update the ssh_authorized_keys value with your own keys, enabling secure SSH access to each node without further configuration. (Highly recommended and there are a lot of guides that will explain how to setup SSH keys if you haven\u2019t already)")),Object(o.b)("li",{parentName:"ol"},Object(o.b)("p",{parentName:"li"},"Update the hostname value to be unique per node.")),Object(o.b)("li",{parentName:"ol"},Object(o.b)("p",{parentName:"li"},"Update the addresses value to be a unique IP per node.")),Object(o.b)("li",{parentName:"ol"},Object(o.b)("p",{parentName:"li"},"Flash the OS image and cloud-init configuration onto the Raspberry Pi using the following command:"))),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),"flash \\\n --userdata setup/cloud-config.yml \\\n ~/Downloads/ubuntu-20.04.1-preinstalled-server-arm64+raspi.img\n")),Object(o.b)("h2",{id:"cluster-configuration"},"Cluster Configuration"),Object(o.b)("h3",{id:"basic-setup"},"Basic Setup"),Object(o.b)("p",null,Object(o.b)("em",{parentName:"p"},"Note: This will initialize your cluster with default CRI & CNI configuration, for more advanced configuration, check the \u201cAdvanced Setup\u201d options.")),Object(o.b)("p",null,"Now we have all our Raspberry Pi nodes running and are configured with a unique hostname, IP, we now need to declare these values in the Ansible inventory file."),Object(o.b)("p",null,"See below for an example of how I configured my 3 master nodes and 1 worker node."),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),"[masters]\nk8s-master-01 hostname=k8s-master-01 ansible_host=192.168.1.121 ansible_user=pi\nk8s-master-02 hostname=k8s-master-02 ansible_host=192.168.1.122 ansible_user=pi\nk8s-master-03 hostname=k8s-master-03 ansible_host=192.168.1.123 ansible_user=pi\n[workers]\nk8s-worker-01 hostname=k8s-worker-01 ansible_host=192.168.1.131 ansible_user=pi\n")),Object(o.b)("p",null,"When the inventory has been configured with all hosts, there is one last thing we must configure. We need to assign a VIP (\u201cVirtual IP\u201d) that will be used to load-balance across the HA master nodes."),Object(o.b)("p",null,"Open masters.yml and configure the keepalived_vip value to an unassigned IP. For my configuration I use 192.168.1.200 .\nRun the following command to verify SSH connectivity."),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),"env ANSIBLE_CONFIG=ansible/ansible.cfg ansible all -m ping\n")),Object(o.b)("p",null,"A successful response should look something like the following:"),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),'k8s-master-01 | SUCCESS => {\n...\n"ping": "pong"\n...\n}\n')),Object(o.b)("p",null,"Note: If your output returns success for each ping then you can continue, otherwise there may be some misconfiguration of either the inventory file or network connectivity issues."),Object(o.b)("p",null,"Now we\u2019ve tested network connectivity we can run the automation scripts that will take care of deploying Kubernetes using the following:"),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),"env ANSIBLE_CONFIG=ansible/ansible.cfg ansible-playbook ansible/playbooks/all.yml\n")),Object(o.b)("p",null,"Once successfully completed you can use kubectl to interact with your Kubernetes cluster:"),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),"kubectl get nodes --kubeconfig ansible/playbooks/output/k8s-config.yaml\n")),Object(o.b)("p",null,"The expected output should look something like the following:"),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),"NAME STATUS ROLES AGE VERSION\nk8s-master-01 Ready master 4m45s v1.18.2\nk8s-master-02 Ready master 70s v1.18.2\nk8s-master-03 Ready master 79s v1.18.2\nk8s-worker-01 Ready <none> 16s v1.18.2\n")),Object(o.b)("p",null,Object(o.b)("strong",{parentName:"p"},"Congratulations!")," You now have a running Kubernetes cluster running on Raspberry Pis."),Object(o.b)("h3",{id:"advanced-setup"},"Advanced Setup"),Object(o.b)("p",null,"This section is an appendage to the \u201cBasic Setup\u201d however, will explore more of the advanced configuration that is available."),Object(o.b)("p",null,"The configuration options can be found in the group_vars folder where you have the files all.yml, masters.yml, and workers.yml. These files contain the configurable variables of each role."),Object(o.b)("h2",{id:"cleanup"},"Cleanup"),Object(o.b)("p",null,"Tear down the cluster and remove everything using the following command:"),Object(o.b)("pre",null,Object(o.b)("code",Object(a.a)({parentName:"pre"},{className:"language-bash"}),"env ANSIBLE_CONFIG=ansible/ansible.cfg ansible-playbook ansible/playbooks/nuke.yml\n")),Object(o.b)("h2",{id:"summary"},"Summary"),Object(o.b)("p",null,"You\u2019ve successfully created a Kubernetes cluster with a highly available topology on Raspberry Pis."),Object(o.b)("p",null,"You now have learned how to configure networking, flash your operating system, set up some basic cluster configuration, and now have the Kubernetes cluster to continue your learning and self-improvement."))}c.isMDXComponent=!0},173:function(e,t,n){"use strict";n.d(t,"a",(function(){return b})),n.d(t,"b",(function(){return m}));var a=n(0),r=n.n(a);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function s(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?l(Object(n),!0).forEach((function(t){o(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):l(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t){if(null==e)return{};var n,a,r=function(e,t){if(null==e)return{};var n,a,r={},o=Object.keys(e);for(a=0;a<o.length;a++)n=o[a],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a<o.length;a++)n=o[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var u=r.a.createContext({}),c=function(e){var t=r.a.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):s(s({},t),e)),n},b=function(e){var t=c(e.components);return r.a.createElement(u.Provider,{value:t},e.children)},p={inlineCode:"code",wrapper:function(e){var t=e.children;return r.a.createElement(r.a.Fragment,{},t)}},d=r.a.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,u=i(e,["components","mdxType","originalType","parentName"]),b=c(n),d=a,m=b["".concat(l,".").concat(d)]||b[d]||p[d]||o;return n?r.a.createElement(m,s(s({ref:t},u),{},{components:n})):r.a.createElement(m,s({ref:t},u))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,l=new Array(o);l[0]=d;var s={};for(var i in t)hasOwnProperty.call(t,i)&&(s[i]=t[i]);s.originalType=e,s.mdxType="string"==typeof e?e:a,l[1]=s;for(var u=2;u<o;u++)l[u]=n[u];return r.a.createElement.apply(null,l)}return r.a.createElement.apply(null,n)}d.displayName="MDXCreateElement"}}]);