Friday, December 24, 2021

Guidewire Jutro DataTable Usage (ver 4.1.1)

Jutro DataTable component is a major UI element when creating jutro pages, DataTable includes build-in pagination, searching, editing and no-data function, understanding how to use and configure jutro DataTable with metadata.json5 is very important.

1. Data source

DataTable metadata json5 definition has a data property, which accepts an array of item. Datatable's column is based on the item's attribute name. If you need to implement your own customized filter function, you can create a function to filter the data first and then feed the filtered data to the Datatable' data property.


2. search/filter 

For Jutro datatable, search and filter means the same. 

ShowSearch property controls whether to show a search input textbox on top of the datatable. If showSearch is set to true, then you can also specify a string on "filterPlaceholder" property to set a placehold text on the search input field. 

By default, when a search text is changed by user, the search criteria will check all records' all columns to get the matched records. However, you can customize the search behavior by specify DataTable's onFilter property to a custom function, and this function will be called whenever user changes the input of search input textbox field. A sample code is shown below:

      {

        "id": "myDataTable",

        "type": "container",

        "component": "DataTable",

        "componentProps": {

          "noDataText": {

            "id": "datatable.nodatatext",

            "defaultMessage": "No data found ...."

          },

          "expandOnRowClick": false,

          "defaultPageSize": 10,

          "pageSizeOptions": [

            10

          ],

          "filterPlaceholder": "",

          "rowIdPath": "publicID",

          "showSearch": true,

          "filterPlaceholder":"Search name, state, or population",

          "headerMultiline": true,

          "onFilter": "filterRow"

        },

    const filterTable = (filterValue) => {

        const filterNumber = parseInt(filterValue, 0);

        return (row) => {

            return row.claimNumber === filterValue;

        };

    };

  const resolvers = {

        resolveCallbackMap: {

            filterRow: filterTable

        }

    };


3. pagination

Pagination is supported by DataTable as build-in feature. The related settings include:

defaultPageSize : 10

pageSizeOptions : [10, 25]

showPagination: true


4. handle row click

When user clicks on a row, a custom callback function can be set to onClickRow property to handle the click, the input parameter is the row clicked.

  {

        "id": "ClaimSummary",

        "type": "container",

        "component": "DataTable",

        "componentProps": {

          "onRowClick": "onClickRow",

          "selectOnRowClick": true

        },

    resolveCallbackMap: {

            filterRow: filterTable,

            onClickRow: onClickRow

    }

    const onClickRow = (row) => {

        console.log(row);

    };


5. defaultSorted

Set datatable's default column for sorting


6. columnsConfigurable

true or false. Decide whether to show the three-bar button on right side of search bar for configuring columns.


7. Table column

Columns in DataTable are defined as an array under datatable's content property. Each column object's component property specify the column type, for example, DisplayColumn. The column object has below properties

header: specifying the header text

path: specify which datatable date object's field is for this column.

sortable: whether to show and support sort on this column

renderCell: specify a callback method to render the cell, the callback function has parameter of row item and row index

Tuesday, December 7, 2021

Guidewire Jutro notes

1. How to get all available properties for a component

Just opening project's framework/packages/components/widgets/folder, and then open the specific component you want to check.


2. Component settings in metadata definition

For object's component settings in metadata json5 file, it will first try to match the jutro component with the same name. If no match found, then it will match the html build-in element. For example, 

"component": "button"

will map to jutro button component, instead of html button element. But

"component": "label"

will map to html label element.


3. Content setting

The jutro framework will do its best to put the content into the mapped html element, for example, button will show the content string as its text. div will just show the content between the open and close tag. 


4. Add custom page/component

To add a new custom page, first create the page component and metadata json5 file. 

Then import the component in app.js file and also add the component into componentMap object.

In app.routes.metadata.json5, add a new path to load the page component for the specific route url path.

Actually this is the same change when running the below generate page command:

jutro-cli generate page


5. Enable guidewiree jutro capability buildin page

In addition to import the page component into app.js and componentMap, as well as updating the app.routes.metadata.json5, you also need to update config.json5's capabilitiesConfig section to set the capability's value to true. Otherwise the page/component jsx file will not be loaded.


6. Dynamic update UI with override in renderContentFromMetadata

When defining UI content with Metadata json5 format, all the values are hardcoded in json5 files. In order to dynamically change the values (for example, set element visible property based on server side response), the jsx file can set override parameter in renderContentFromMetadata method. For example, the below code sets claim element's visible property based on the return value of isClaimVisible()

       Claim: {
            data: claimDataTable,
            visible: isClaimVisible(),
}
Note, the method will only be called once when the UI is rendered from metadata, once the rendering is finished, even if the function returns a different value, it will not be called again automatically.
In order to be sure UI will reflect the underling data change, in the data change handler function, it should call useState() method to notify react to refresh the UI, so the metadata will be evaluated again. 
Basically, values set in override parameter is same as other react component properties, and they are read only when passed to child component for initialization. 

7. Difference between to and href for Link component
When href attribute is set for Link component, it will cause to reload the page on that url, which will download the full index.html and js files.
When to attribute is set for Link component, it will stay in the current html page, and only reload the component based on app.js router configuration. So it is much fast and user friendly.

Guidewire jutro metadata json5 object types

When defining objects in Jutro with metadata json 5 schema, there are 5 types available. Since there are only 5 types, so the exact html tag type of the object depends on both object's type and component setting, and Jutro will make the best guess based on those information.  

1. Elements

This type is for static object that do not offer a user input, like label or badge. Element type object does not have content property. 

2. Container

Container object contains array of elements, other containers, fields, iterables. Container object always has a content property to define its content.

3. Field

Field object is used to describe user input elements. Field type usually do not have content property, but have a label property depending on its component setting.

4. Action

Action object is used to describe interactive or action components. For button component, its content property sets the button text.

5. Iterables

Iterable type mainly used with Accordion, tabset and table to show repeated items. It also has build-in support for empty content.

6. layout

layout type is used for setting the elements layout.


Monday, December 6, 2021

Guidewire Jutro metadata: how to set property to json5 component

1. Set component prop in metadata json5 file

When creating component using Guidewire Jutro metadata json5 format, for simple element, for example, label component of element type, the label text is set by "content" attribute, and other attributes are set by componentProp attribute as below:

 {

        id: 'myLabel',

        type: 'element',

        component: 'label',

        content: 'mylabel name',

        componentProps: {

          for: 'myfor',

        },

        selfLayout: {

          component: 'div',

          componentProps: {

              someattr: 'mycustattr',

          },

        },

      },


This indicates jutro (4.1.1)  just puts whatever componentProps settings into the element's html without validation whether the attributes actually exist or not.

Note the selfLayout node defined in the metadata creates a new wrapping element for the current element. if the node is deleted, then <div someattr='mycustattr'> element will also be deleted.


2. Set component in jsx with override and resolver

In many cases, we need to set component properties dynamically in jsx files for example, set the callback method for button element. This can be done by passing resolver parameter in jsx's renderContentFromMetadata method. For example, if metadata sets button's callback to a string, the string needs to map to a method defined in jsx file as below

      {

        id: 'btnFileCLaim',

        component: 'button',

        type: 'element',

        content: 'button name',

        componentProps: {

          size: 'small',

          type: 'primary',

          onClick: 'clickme',

        },

      },


in component jsx file:

const MyTestComponent = () => {

  const onclickme = () => {
    console.log('button clicked');
  };

  const override = {
  };

  const resolvers = {
     resolveClassNameMap: styles,
     resolveCallbackMap: {
      clickme: onclickme,
    }
 };

 return renderContentFromMetadata(uiMetadata.viewClaimList, override, resolvers);
};

export default MyTestComponent;


When overriding property values, the id is used as key, and there is no need to specify componentProps. For example, in above sample, the below code can be used to override the button's label and type property

const MyTestComponent = () => {

    const onclickme = () => {
        console.log('button clicked');
    };

    const override = {
        codelessCardTitle: {
            content: 'myoverridecontent',
        },
        btnFileCLaim: {
            content: 'mybuttonoverridename',
            type: 'secondary',
        },
    };

    const resolvers = {
        resolveClassNameMap: styles,
        resolveCallbackMap: {
            clickme: onclickme,
        }
    };

    return renderContentFromMetadata(uiMetadata.viewClaimList, override, resolvers);
};

Wednesday, November 10, 2021

How React component pass data around

For React components, the basic way to receive data is passing them as component attributes when creating the component in its parent's html render method. Those attributes will pass into the component's js file as props parameter.

Within the component js file's render method, internal data can be handled by useState hook, which update the user input into component state, which leads to update the ui.

In case when a react component needs to pass data out to its parent, the parent can pass a callback method to the component as a component attribute, then when some event happens, the component can call the callback method and pass the data as callback parameter. Then the parent component can use the callback method's implementation to handle the data specified in the callback method's parameters. For example, the parent can update its own data managed by the useState hook. 

To avoid passing property value from top to low layer components, react application can use useContext to facilitate passing data directly from top component to lower layer components.


Saturday, November 6, 2021

Using custom .env file for react project

By default, react project supports .env, .env.development, .env.production and .evn.test for different build and test scenario. However, sometimes, you may need additional testing and deployment configuration with custom env file. The following steps can be used to setup custom react env files:

1. run below command to add env-cmd package to your react project

npm install env-cmd

2. create your custom env file, for example, .evn.staging in your project in the same folder as other .env files. Then add the environment variables and values in the file in the same way as other .env file.

3. in the project's package.json file's script section, add "env-cmd -f YourCustomEnvFileName" parameter before other command parameter to indicate which .env file should be used for the command, for example:

 "start": "env-cmd -f .env.staging react-scripts start", 

will start the react app by using the configuration in evn.staging file.

Friday, October 15, 2021

Understanding React hooks: useState, useEffect

useState:

useState is for managing the component states. Note setState is not synchronous method, so the state value will not be updated immediately after the setState method returns. However, eventually the state will be updated in the following rendering.

Although useState is asynchronous, it will be executed under the same context when the method is initially called. If it is called in an event handler, then the async state updating will be executed from the event handler's callstack. If it is called from function component's main rendering function, then the async state updating will also happen in the main rendering function.    

If setState is called in the function component's main method, then it will cause runtime errors if the setState method internally calls other component's setState method, and triggers the updating of other components. It is a good practice to always call setState method in event handler, and also do not call other component's setState method within current component's setState method.

useEffect:

useEffect's main purpose is providing a place to execute some code outside of the function component's main render function. This will speed up and simplify the logic in the main render function, in addition, certain functions are not allowed to be called from the function component's main render function, for example, calling other component's setState method within the main render function is not allowed. So useEffect is a good place to execute the code after the rendering is finished. 

useEffect can be configured by the second parameter to decide when its logic will be called, it can be called after every rendering (without providing second parameter), only after first rendering (providing an empty array in the second parameter), or whenever certain states or variables have been change (including the states or variables in the second array parameter).

When an event handler is included in the array of the second parameter of useEffect, whenever a new event handler is created during executing the component render function, the function of useEffect will always be called because the event handler has been updated. To avoid executing the body function of useEffect triggered by the new event handler, useCallback hook can be used to always return the same event handler object during different rendering of the same component. 

The function passed to useEffect may return a clean up function. The returned function will be called automatically when the component is removed from UI, or when next time the effect body function is called again.

Saturday, October 2, 2021

Apply CSS style to react material ui component

There are few ways to customize css style for native html elements (like button, div, etc) and React Material UI components (like Button, TextField, etc). Note when styles are specified as javascript or typescript object in Material UI syntac, the style attribute name is different from html css style attribute name, please refer to below link for more details.

https://mui.com/system/properties/

 

1. For Material UI components, if the css style applies to global scope, the best way to set it is in custom theme file as shown below:

const myTheme = createTheme({
    components: {
      MuiButton: {
        defaultProps:{
          disableElevation: true,
        },
        styleOverrides: {
          root: {
            borderRadius: '2em',
            width: '9em',
            textTransform: 'none',
            border: '1px solid #0076bf',
            color: '#fff',
            backgroundColor: '#0076bf',
            "&:hover" :{
              color: '#0076bf',
              backgroundColor: '#fff',
              borderr: '1px solid #0076bf',
            }
          }
        }
      }
    }
  });

  export default myTheme;

This method requires using Material UI style attribute name instead of html css style attribute name. For example, to change the background color, 'background-color' does not work, only backgroundColor will work. (The test shows bgcolor also does not work when specified in theme file, only backgroundColor works here).

If you need to customize native html element style in global css scope, then you can just use the old way to set it in a regular html css file.

2. When applying css style as named css class to certain elements including both Material UI component and native html elements.

This is a simple way to reuse the css style defined for regular html and css web projects. The style must be defined with specific css class name in a regular css file. Note in this way, you define css styles using regular html css attribute name and syntax, not Material UI's attribute names. Then in js (or jsx) file, importing the css file and apply the class styles to the corresponding elements.

The element must specify the class attribute name as 'className' instead of the regular 'class'. The reason of changing the name of 'class' to "className" is, js or jsx file is not regular html file, those jsx elements are not real html elements, and react needs to translate these jsx attributes to the regular html element attributes when running the function component method, where className will be translated to html 'class' attribute.  The below code applies row and mb-2 class style to div element

<div className="row mb-2">

As a react expression, className can also take react expression as value. React expression is wrapped by {}, so react expresion of {"row mb-2"} will just return the string of value of "row mb-2". So it has the same effect as 
<div className={"row mb-2"}

More important, when using react expression as class value, back tick can be used to dynamically set a sub react expression as class name, the sub react expression must be wrapped with ${}. For example:
<div className={`row mb-2 ${!isValid? 'invalidClassName' : 'validClassName'}`}

The css style defined in css file applies to global scope, so any elements having those style classe names will have those styles classes applied to them.
 
3. If you only want to apply a particular css style to a certain elements (native html elements or Material UI components), then you can apply the them as inline css style. This method specifies the style as React object, so the style attribute name must use the name defined by Material UI, not the html css style attribute name. 

The style attribute's value is a react expression (wrapped by {}) of a javascript object (wrapped by another {}), so the value is wrapped in {{}}.

<label style={{backgroundColor:'red', width:'100px'}}>hello</label>

The limitation of this method is it is hard to set complex styles, for example, no easy way to setup hover state styles for the elements.

4. For material ui components (not native html element), it can also set sx (system) attribute to define custom style based on theme. Note the style name used for sx needs to use material ui's style attribute name. The style will only apply to the current Material ui component.
<Stepper activeStep={activeStep} sx={{ pb: 3 , bgcolor: 'text.secondary'}}>
<Button sx={{
                                      color:'#0076bf',
                                      backgroundColor: '#fff',
                                      width: '9em',
                                      border: '0px',
                                      cursor: 'pointer',
                                      '&:hover':{
                                        textDecoration: 'underline',
                                      }
                                }} onClick={props.onPrev}>{t("button.previous")}</Button>
Please refer to https://mui.com/pt/system/the-sx-prop/ for available style names to be used by sx.


Wednesday, September 29, 2021

React: useContext usage note

useContext provides an simple way to allow child components to directly access high level parent's data and methods without relaying the information through multiple levels. Context in react has two parts, one is context provider to wrap the root level parent component, and one is useContext to provide access for low level child components. The react provides a key function of createContext function, which returns a context object, the context object includes everything for getting the context provide, and specify the context's data member types, and accessing the context data members.
 
Regarding the context provider, contextProvider is a just regular react component, generated by createContext method. The only unique thing about it is, it has a property named as value, which must be set to an instance of the context type specified in createContext method. In the application's root component, any children wrapped directly or indirectly below contextProvider component can access the context data member using useContext hook.

useContext works in similar way as react component props, if context method provides set method, then children component can call this method to change values implemented in context provider component. However, if the context provider just sets the value as a regular js variable, then updating the js variable's value or object reference will not trigger rendering the provider component and its children components. Basically useContext just provides a special prop that can be accessed by all children components with useContext hook. Similar to other regular component properties, context.Provider just creates a snapshot of the current value that can be accessed by the children components.

So, unlike useState, context does not participate in the React change detection, there is no set method to update context value as useState does. A common practice is implementing the context's value using the useState hook in the provider component, so once state object is updated by the children component, it will trigger the rendering of the provider component, which will lead context.Provider component and its children component to update to the new state value.  

Note, in the root component that sets the context provider, there is no need to call useContext to get the context value, as it already has the context value to set as attribute of context provider. But in case useContext is called to get the context value, you will see the default value instead of the value set by myContext.Provider is returned. The reason is as when useContext(myContext) is called, which happens at the beginning of component function, myContext.Provider has not been executed, so it makes sense to return the default value by useContext(myContext) method. However, any children component wrapped within myContext.Provider will get the new value set by myContextProvider. 


React: useState Hook change detection

[myStateValue, setMyStateValue] = useState(myObject);

Component will render itself and its children components if its state has changed with useState hook.

If the state value is primary type, like string or number, then call setMyStateValue method will trigger the render if the value is not changed, then calling setMyStateValue will not cause re-rendering.

If the state value is an object, when calling setMyStateValue method, if both the object reference and object value have not been updated, then it will not cause an re-render. If the object's reference is not changed, but the object's property value is changed, then setMyStateValue will not cause a re-rendering due to the updated property values. The only case to lead to rendering is when the object reference is changed.  This means React only monitors and detects the reference change for useState object type, and ignore the object's property changes.

Calling setMyStateValue method will only cause re-rendering of the current component which defines the state object, which will also re-render all the children components. But it will not cause re-rendering of the current component's parent component. 

When calling setMyStateValue method, the new state parameter (myNewState) must contain the full value of the new state object, as it will replace the original state object. 

setMyStateValue(myNewState);

A better way to call setMyStateValue is passing in a function parameter, which takes a parameter of previous state, so that you can build the new state object based on the latest state object, and then return the new state object from the function parameter.

setMyStateValue( (previousState) => {

    return { ... previousState, propertyName:updatedPropertyValue};

}

React: Props are read-only

Props are read-only

Props passed in to component are read-only, they can never be changed within the received component. If a component tries to update the prop value passed to it , an exception will be thrown for the error "cannot assign to read-only property". Note, if the prop value is an object, then within the received component, it can change the object's property value without causing any exception, however, React will not detect the change of the prop's value, and will not render the component again for this change.

In the parent component which sets the props, if the prop is set from a regular js let variable, then after the child component is rendered, updating the let variable's value, i.e the props value passed to the child component, React will not detect the prop change and will not re-render the child component based on the updated prop value. The prop value passed to child component is just a snapshot when rendering the child component, and the parent component will not track any future change of a regular js variable. 

In short, React does not care about regular js variable change, and will not render again based on those changes.

In order to let React knows the change, useState hook should be used.



Sunday, September 26, 2021

Understand msal-react package used for react js project



In order to use Azure authentication for react project, two packages are needed to be installed in react project.

npm install @azure/msal-react @azure/msal-browser

Package @azure/msal-browser is the base package, which implements the basic function for msal authentication for javascript project, including server side node projects and client side html/js project.

Package @azure/msal-react is wrapper package around @azure/msal-browser, it provides react specific feature, like react hook and context, so react developers can easily integrate the msal features into the react projects.

To get familiar with msal-react package, developers should first read the document in 

https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react#advanced-topics

https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-react

Developer may also want to refer to the document at the below link for msal-browser package, in order to understand certain inherited functions provided by msal-react package.

https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser


  

 


Tuesday, July 27, 2021

Difference between Angular and React

React is a library for building User Interface, while Angular is a framework to build the entire solution project.

As a library, React only handles user interface interaction. It does not handle network requests, url routing, and resource loading. 

On the contrary, Angular is a js framework. As a framework, it includes all the features to create an application, including UI interaction, UI component, http request, routing, and authentication, etc. Framework requires the project's structure to follow its predefined way to to handle different things.

The advantage of using a framework is, it already has the solution for many common features required by regular project, and those solution are verified by many other projects, so following the direction of a framework will not have too much risk during the development, the framework already decides many aspects of the project, and developers do not need to come up with a solution by themselves for each requirement, so the project's quality is already guaranteed by the framework. 

However, framework is hard to customize or extend, so if the project has some unique feature that is not covered by the framework's solution, then it is hard for developers to customize or extend the framework to handle the unique requirement. 

In addition, for some simple and small project, it may not need the full features of a framework, in this case, using react as a library is also a good choice for quick starting.

For the same reason, react is easier to learn due to its limited scope, while angular has a deep learning curve due to its large feature set and complex. 

From a developer's view and experience, overall, react is a better choice due to its simple logic and syntax. Angular has many unnecessary steps developer need to do, for example, you have to register packages in module.ts in addition to the simple js import, etc.     


Friday, January 15, 2021

Note for Kubernetes administration

Kubernetes can be used to manage container based cluster deployment, so hundred of thousands of containers can communicate, discover, and function as expected under the control of kubernetes. Although it sounds complex, but fundamentally it is just a software application managing the running of your application in containers.


1. Installation with kubeadm

There are three main tools for kerbenetes installation and management, they need to be installed on both master and worker nodes with below command

apt-get install -y kubeadm=1.18.1-00 kubelet=1.18.1-00 kubectl=1.18.1-00

1.1 kubeadm:  for installation of master node

A regular linux package running on your virtual machines or local boxes's terminal console to install the kubernetes software, it sets up master node's kubernetes cluster manager, and also allow other VM to join the kubernetes as worker node. Before installing kubeadm, docker needs to be installed first on the VM, which is the basic running unit in the kubernetes cluster 

//1. update apt-get

sudo -i

apt-get update

apt-get upgrade -y

//install docker

apt-get install -y docker.io

//2. prepare kubernetes repo by adding a new line in the below file

//install vim if it is not installed on the current node

apt-get install -y vim

vim /etc/apt/sources.list.d/kubernetes.list

deb http://apt.kubernetes.io/ kubernetes-xenial main

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -

apt-get update

//3 install kubeadm, kubelet, kubectl

apt-get install -y kubelet kubeadm kubectl

apt-mark hold kubelet kubeadm kubectl

//4 install network add on

wget https://docs.projectcalico.org/manifests/calico.yaml


//uncomment below lines in the file to used for kubeadm init pod-network-cidr parameter

            - name: CALICO_IPV4POOL_CIDR

              value: "192.168.0.0/24"

//get ens4 ip address, for example, 10.0.0.6

ip addr show


//add the host ip to host table for k8smaster

root@master: ̃# vim /etc/hosts

10.0.0.6 k8smaster


//create kubeadm-config.yaml file as below at /root folder

apiVersion: kubeadm.k8s.io/v1beta2

kind: ClusterConfiguration

kubernetesVersion: 1.20.0

controlPlaneEndpoint: "k8smaster:6443"

networking:

  podSubnet: 192.168.0.0/24

// run kubeadm init to start cluster, the output has information for how to add other nodes

root@master:~# kubeadm init --config=kubeadm-config.yaml --upload-certs 


root@master:~# exit


//allow no root user to manage cluster

mkdir -p $HOME/.kube

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

sudo chown $(id -u):$(id -g) $HOME/.kube/config


//apply network addon calico

sudo cp /root/calico.yaml .

kubectl apply -f calico.yaml


//verify master node, and major kube services (calico, coredns, etcd, apiserver, kubeproxy, scheduler) are ready 

kubectl get nodes

kubectl -n kube-system get pods


1.2 kubeadm:  for installation of worker node

//1. update apt-get and install docker

sudo -i

apt-get update

apt-get upgrade -y

//install docker

apt-get install -y docker.io

//2. prepare kubernetes repo by adding a new line in the below file

//install vim if it is not installed on the current node

apt-get install -y vim

vim /etc/apt/sources.list.d/kubernetes.list

deb http://apt.kubernetes.io/ kubernetes-xenial main

apt-get update

//3 install kubeadm, kubelet, kubectl

apt-get install -y kubelet kubeadm kubectl

apt-mark hold kubelet kubeadm kubectl

//4 run the below commands on master node to get master ip, for example 10.0.0.6

ip addr show ens4

//use below command to get token from master

sudo kubeadm token list

//if the token is expired, use the below command to generate new one

sudo kubeadm token create


//run the command to get cert hash

openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/ˆ.* //'

//output is (stdin)= fb5f18060d9c9363763da1e0ddf164dd1b41e640204d0e5ab01c366c3f5b944b


//5 go back to worker node, to add master node ip as k8smaster to host table

root@worker: ̃# vim /etc/hosts

//add below line

10.0.0.6 k8smaster


//6 add worker node to cluster

kubeadm join k8smaster:6443 --token xwqbt0.ig282rtsalg84ndw --discovery-token-ca-cert-hash sha256:fb5f18060d9c9363763da1e0ddf164dd1b41e640204d0e5ab01c366c3f5b944b


root@worker: ̃# exit


//6 run below command on master node to confirm the worker node is joined

student@master:~$ kubectl get nodes

1.3 cluster upgrade

1.3.1 First upgrade kubeadm package on master node

kubeadm upgrade plan

//get upgrade information

apt-get install kubeadm=1.19.0

//download new version of kubeadmin on master node

1.3.2 upgrade cluster

kubeadm upgrade apply v1.19.0

//apply the kubeadm upgrade of master node, running the command on master node


1.3.3 upgrade kubelet to new version on master node

apt-get install kubelet=1.19.0

or

apt-get upgrade -y kubelet=1.19.0


//restart kubelet

sudo systemctl daemon-reload

sudo systemctl restart kubelet


1.3.4 upgrade kubelet on worker node, 

//first drain the worker node from master, then run "ssh workernodename" to login to worker node

apt-get upgrade -y kubelet=1.19.0

apt-get upgrade -y kubeadm=1.19.0

kubeadm upgrade node

//the above command also applies to worker node and other control plan (not master control node) 

sudo systemctl daemon-reload

sudo systemctl restart kubelet

at last, uncordon the worker node from master node


2. Manage cluster with kubectl

After kubernetes is installed, on the master node's VM console terminal, user can use kubectl to communicate with kubernetes to manage kubernetes cluster, such as, create/delete nodes/pods, get info from nodes/pods, scale up and down. Internally, kuberctl sends http requests to kubernetes API server, which communicates to different services and controllers to be sure all worker nodes are in good state.

To get the version for yaml file on different resource type, run the below command

kubectl explain typeofresource, for example

kubectl explain replicaset 

2.1 creating pod

// create a pod with run command

kubectl run mypodname --image dockerimagename

kubectl run mytestpod --image=nginx --dry-run=client -o yaml > mypod.yaml 


//get pod info

kubectl get pods 

kubectl get pods -o wide

kubectl describe pod podname

//delete pod

kubectl delete pod podname

//use dry run to generate pod create yaml file

kubectl run mytestpod --image=nginx --dry-run=client -o yaml > mypod.yaml 

//generated yaml file as below

apiVersion: v1

kind: Pod

metadata:

  creationTimestamp: null

  labels:

    run: mytestpod

  name: mytestpod

spec:

  containers:

  - image: nginx

    name: mytestpod

    resources: {}

  dnsPolicy: ClusterFirst

  restartPolicy: Always

status: {}


// create a pod with a yaml file

kubectl create -f pod.yml

//apply the content in yaml file

kubectl apply -f pod.yaml

//update pod config

kubectl edit pod podname

//select pod based on label matching

kubectl get pod --selector env=prod,bu=finance,tier=frontend

//generate a yaml file based on exist pod

kubectl get pod mypod -o yaml > mypod.yaml


//create a pod with resource limit and request

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

2.2 Create Replicaset

Replicaset yaml file's spec section must include pod creating template, selector and replica number fields

kubectl create -f replicaset -f myreplicaset.yaml

kubectl get replicaset -o wide

kubectl delete replicaset myreplicasetname

kubectl replace -f myreplicasetfilename.yaml

kubectl scale replicaset --replicas=10 myreplicasetname

kubectl  describe replicaset myreplicasetname


//yaml file to create replicaset

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3


2.3 create Deployment

kubectl create deployment --image=nginx mynginx --replicas=4

kubectl create deployment --image=nginx mynginx --dry-run=client -o yaml

kubectl create deployment --image=nginx mynginx --dry-run=client -o yaml > nginx-deployment.yaml

kubectl scale deployment mynginx --replicas=4

//expose deployment as clusterIP on a particular port

kubectl expose deployment mynginx --port 80

kubectl get deployment mydeployment -o yaml

kubectl edit service mynginx


kubectl edit deployment mydeployment



2.4 namespace

kubectl get pods --namespace=dev

kubectl config set-context $(kubectl config current-context) --namespace=dev

kubectl get pods --all-namespaces


2.5 get statistic from pod and node

kubectl top node

kubectl top pod


2.6 get log

kubectl logs -f podname containername


2.7 rollout deployment

Rollout operation is based on the deployment name, the deployment with the same name can be restart pause, resume, undo, etc

//roll back a deployment to previous change

kubectl rollout undo deployment/mydeployment


2.8 command and arg

In pod yaml file, it can uses command and args to replace Docker file's ENTRYPOINT (execution command) and CMD (argument) field

apiVersion: v1
kind: Pod
metadata:
  name: command-demo
  labels:
    purpose: demonstrate-command
spec:
  containers:
  - name: command-demo-container
    image: debian
    command: ["printenv"]
    args: ["HOSTNAME", "KUBERNETES_PORT"]

In pod yaml file, it can uses command and args to replace Docker file's ENTRYPOINT (execution command) and CMD (argument) field

To execute a shell command in container

kubectl exec mypodname -c mycontainername -i -t -- mybashcommand

//run interactive bash command
kubectl exec mypodname -it -- sh

2.9 ConfigMaps for Environment variables

kubectl create configmap myconfigmap --from-literal=mykey1=myvalue1 --from-literal=mykey2=myvalue2

kubectl create configmap myconfigmap --from-file=mypropertyfile


Using yaml file to create config map

kubectl create -f myconfigmapyamlfile

apiVersion: v1
kind: ConfigMap
metadata:
  name: game-config-env-file
  namespace: default
  resourceVersion: "809965"
  uid: d9d1ca5b-eb34-11e7-887b-42010a8002b8
data:
  allowed: '"true"'
  enemies: aliens
  lives: "3"

sample pod yaml file to set environment from config map

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - configMapRef:
          name: special-config
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      env:
        - name: SPECIAL_LEVEL_KEY
          valueFrom:
            configMapKeyRef:
              name: special-config
              key: special.how
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: env-config
              key: log_level

2.10 secret for pod

kubectl create secret generic mysecret --from-literal=mykey1=myvalue1 --from-literal=mykey2=myvalue2

kubectl create secret myconfigmap --from-file=mypropertyfile

kubectl create -f mysecretfile

Generate hash value for secret file
echo -n "myvalue" | based64

decode hash value 
echo -n 'myhashedvalue" | base64 --decode

Set environment variable from secret
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - secretRef:
          name: mysecret
  restartPolicy: Never
create docker registry secret
kubectl create secret docker-registry private-reg-cred --docker-username=docker_user --docker-password=dock_password --docker-server=myprivateregistry.com:5000 --docker-email=dock_user@myprivateregistry.com

2.11 certificate management

kubectl get csr

get certificate signing request


based 64 encode the csr and remove \n

cat john.csr | base64 | tr -d "\n"


kubectl certificate approve mycsrname

signing a certificate request and create the signed certificate


kubectl certificate deny mycsrname

deny a certificate signing request


kubectl certificate delete csr myscrname

delete certificate signing request


After the certificate is signed, use the below command to view the cert, the generated cert is in status/certificate field. The content is base 64 encoded,  it need to be base64 decoded.

kubectl get csr mycsrname -o yaml

Certificate signing request yaml file

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: john
spec:
  groups:
  - system:authenticated
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZqQ0NBVDRDQVFBd0VURVBNQTBHQTFVRUF3d0dZVzVuWld4aE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRgpBQU9DQVE4QU1JSUJDZ0tDQVFFQTByczhJTHRHdTYxakx2dHhWTTJSVlRWMDNHWlJTWWw0dWluVWo4RElaWjBOCnR2MUZtRVFSd3VoaUZsOFEzcWl0Qm0wMUFSMkNJVXBGd2ZzSjZ4MXF3ckJzVkhZbGlBNVhwRVpZM3ExcGswSDQKM3Z3aGJlK1o2MVNrVHF5SVBYUUwrTWM5T1Nsbm0xb0R2N0NtSkZNMUlMRVI3QTVGZnZKOEdFRjJ6dHBoaUlFMwpub1dtdHNZb3JuT2wzc2lHQ2ZGZzR4Zmd4eW8ybmlneFNVekl1bXNnVm9PM2ttT0x1RVF6cXpkakJ3TFJXbWlECklmMXBMWnoyalVnald4UkhCM1gyWnVVV1d1T09PZnpXM01LaE8ybHEvZi9DdS8wYk83c0x0MCt3U2ZMSU91TFcKcW90blZtRmxMMytqTy82WDNDKzBERHk5aUtwbXJjVDBnWGZLemE1dHJRSURBUUFCb0FBd0RRWUpLb1pJaHZjTgpBUUVMQlFBRGdnRUJBR05WdmVIOGR4ZzNvK21VeVRkbmFjVmQ1N24zSkExdnZEU1JWREkyQTZ1eXN3ZFp1L1BVCkkwZXpZWFV0RVNnSk1IRmQycVVNMjNuNVJsSXJ3R0xuUXFISUh5VStWWHhsdnZsRnpNOVpEWllSTmU3QlJvYXgKQVlEdUI5STZXT3FYbkFvczFqRmxNUG5NbFpqdU5kSGxpT1BjTU1oNndLaTZzZFhpVStHYTJ2RUVLY01jSVUyRgpvU2djUWdMYTk0aEpacGk3ZnNMdm1OQUxoT045UHdNMGM1dVJVejV4T0dGMUtCbWRSeEgvbUNOS2JKYjFRQm1HCkkwYitEUEdaTktXTU0xMzhIQXdoV0tkNjVoVHdYOWl4V3ZHMkh4TG1WQzg0L1BHT0tWQW9FNkpsYWFHdTlQVmkKdjlOSjVaZlZrcXdCd0hKbzZXdk9xVlA3SVFjZmg3d0drWm89Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - client auth

2.12 cluster config

kubectl config view
view the cluster config file in ~/home/.kube/config

The current-conext element specifies the current context used by kubectl.

To switch to a different context, using the below command:
kubectl config use-context myusername@myclustername
kubectl config --kubeconfig=/mypath/myconfigfile use-context mycontextname

2.13 volume
1. volume has the same lifetime of a pod, it outlives any containers that run within the pod, and the data is preserved across container restarts. When a pod ceases to exist, the volume is destroyed.
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
      # this field is optional
      type: Directory

2. Persistent Volume

Persistent Volume has a lifecycle independent of any individual pod. It is a resource provisioned by cluster admin. A persistentVolumeClaim is a request by a pod to ask for the persistent volume resource.

persistent volume yaml file sample
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 172.17.0.2

PersistentVolumeClaim yaml sample

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}
Pod yaml sample to request a persistentVolumeClaim
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

StorageClass yaml file sample

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
  - debug
volumeBindingMode: Immediate


3. Scheduler

3.1 Scheduler is used to decide which pod runs on which node.
The basic scheduler uses nodeName to select a node with matched name, and run on it.

3.2 taints and toleration

Taint allows a node to define a rule to expel all the pods it does not want, so only those pods with matched toleration can be hosted, in this case, node is rule decider. taint has three effects:

NoSchedule

PreferNoSchedule

NoExecute

//set taint for a node,  which means no pod can be schedule onto node1 unless it has a match toleration

kutectl taint nodes node1 app=blue:NoSchedule


Pod with matched toleration can be hosted in the nodes with taint

apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: "example-key"
operator: "Exists"
effect: "NoSchedule"

another toleration operation is equal, which has the same result as exists

tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"

3.3 node selection and affinity

Pods selection and affinity let pods decide which node can host it, so it expels  all other node it does not want. In this case, pod is decision maker.

3.3.1 pod creation yaml file can contain nodeSelector element which indicates which node can be used to host the pod.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

3.3.2 node affinity

Node affinity are configuration which uses complex express in pod definition to match which node can host the pod

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent

3.4 resource request and limit

Pod can specify its resource request and resource limit to let scheduler to decide which node can be used to put the pod 

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

3.5 node maintenance

kubectl drain nodename --ignore-daemonsets --force

Move current pods in the specified node to other nodes, and also mark the specified node as unable to schedule.

kubectl cordon mynode

mark the specified node as unable to schedule.

kubectl uncordon mynodename

mark a node to be able to schedule


Backup cluster data

ETCDCTL_API=3 

etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key snapshot save /opt/snapshot-pre-boot.db

restore to a new data folder

etcdctl snapshot restore restorefilepath --data-dir=newdatapath

edit etcd manifest file to set data directory to the new data folder

vim /etc/kubernetes/manifests/etcd.yaml


4. Node

In Kubernetes, a node is a VM or a local computer hosting pods. Kubernetes has two types of nodes: master and worker node (), A node can have multiple pods, and each pod can have multiple containers.

kubectl get nodes

Kubernetes scheduler decides which node will host a particular pod.

Label the nodes for pod selection

kubectl label nodes mynodename labelkey=labelvalue

To execute bash command on a node in cluster, use ssh command. 

To execute bash command on a container in a pod, use kubectl exec -it podname -- sh


Master:

master node manages the kubernetes cluster. It contains a component Kube-APIServe to communicate with other management components and all worker nodes. 

Master node contains services to allow pods to communicate to each other and to the outside world, master node also contains controllers to manage and upgrade the deployment of pods

Worker:

worker nodes contain  one or more pods, a pod contains one or more (docker) containers. A pod has a unique ip address and mounted storage shared by all containers within it. 


5. Network configuration:

Kube-Proxy
kube-proxy is used for one pod to access another pod, however, as pod's ip address may change anytime, so calling pod cannot directly connect to target pod using pod's ip address. Instead, the service is used to link the fixed service ip address to dynamic pod ip address. Kube-proxy has a service table to map each service to the current available pod ip address which provides the specific service.


Cluster IP

cluster ip groups the pods with same function and expose them with a single ip address (cluster IP) and port number. When requests sent to the cluster ip and port, it will be redirect to one of matched pods. on the targetPort.


The output of 

kubectl describe service myclusteripservice -o wide

includes endpoint field, which is the matched pods' ip address and ports.


//create a service of ClusterIP to expose the pods on a particular port 

kubectl expose pod myredis --port=6379 --name=redis-service --dry-run=client -o yaml 


Cluster IP is used for internal communication between function providers to function consumers.

yaml file to create cluster ip

apiVersion: v1

kind: Service

metadata:

  name: backend

spec:

  type: ClusterIP

  ports:

    - targetPort: 80

      port: 81

  selector:

    app: mybackendapp


NodePort

NodePort is a service running on a worker node which listens on a particular port on the node, and then forward the requests received on that port to one of matched pod. The client request must send to a particular pod's ip address and node port. 

The NodePort definition also defines the listening port on the matched pod using targetPort field.

yaml file to create service:

apiVersion: v1

kind: Service

metadata:

  name: myservice

spec:

  type: NodePort

  ports:

    - targetPort: 80  //listening port on the matched pods for handling external request

      port: 88  //service port number to be connected by pods or other services

      nodePort: 30008 //listening port on node for accepting external request

  selector:

    app: myapp  //label for marching pod


yaml file for creating matched pod:

apiVersion: v1

kind: Pod

metadata:

  creationTimestamp: null

  labels:

    app: myapp  //matching label for pods

  name: mytestpod

spec:

  containers:

  - image: nginx

    name: mytestpod


Client can send requests from anywhere to node's external ip address and Nodeport (30008) in order to access the pod's listening port 80.

External load balancer

Using external loadbalance with kubernetes can expose services to external with NodePort, the client request will be assigned to different NodePort port to spread the load balance.


Ingress (Internal loadbalancer)

Ingress within kubernetes receives the external requests and distribute the requests to different services' cluster IP based on ingress rule. It is preferred than external loadbalancer.