One place for hosting & domains

      November 2019

      How to Deploy Istio with Kubernetes


      Updated by Linode Contributed by Linode

      Istio is a service mesh, or a network of microservices, that can handle tasks such as load balancing, service-to-service authentication, monitoring, and more. It does this by deploying sidecar proxies to intercept network data, which causes minimal disruption to your current application.

      The Istio platform provides its own API and feature set to help you run a distributed microservice architecture. You can deploy Istio with few to no code changes to your applications allowing you to harness its power without disrupting your development cycle. In conjunction with Kubernetes, Istio provides you with insights into your cluster leading to more control over your applications.

      In this guide you will complete the following tasks:

      Caution

      This guide’s example instructions will create several billable resources on your Linode account. If you do not want to keep using the example cluster that you create, be sure to delete it when you have finished the guide.

      If you remove the resources afterward, you will only be billed for the hour(s) that the resources were present on your account. Consult the Billing and Payments guide for detailed information about how hourly billing works and for a table of plan pricing.

      Before You Begin

      Familiarize yourself with Kubernetes using our series A Beginner’s Guide to Kubernetes and Advantages of Using Kubernetes.

      Create Your Kubernetes Cluster

      There are many ways to create a Kubernetes cluster. This guide will use the Linode k8s-alpha CLI.

      1. To set it up the Linode k8s-alpha CLI, see the How to Deploy Kubernetes on Linode with the k8s-alpha CLI guide and stop before the “Create a Cluster” section.

      2. Now that your Linode K8s-alpha CLI is set up, You are ready to create your Kubernetes cluster. You will need 3 worker nodes and one master for this guide. Create your cluster using the following command:

        linode-cli k8s-alpha create istio-cluster --node-type g6-standard-2 --nodes 3 --master-type g6-standard-2 --region us-east --ssh-public-key $HOME/.ssh/id_rsa.pub
        
      3. After the cluster is created you should see output with a similar success message:

          
        Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
        Switched to context "[email protected]".
        Your cluster has been created and your kubectl context updated.
        
        Try the following command:
        kubectl get pods --all-namespaces
        
        Come hang out with us in #linode on the Kubernetes Slack! http://slack.k8s.io/
        
        
      4. If you visit the Linode Cloud Manager, you will see your newly created cluster nodes on the Linodes listing page.

      Install Helm and Tiller

      Follow the instructions in the How to Install Apps on Kubernetes with Helm guide to install Helm and Tiller on your cluster. Stop before the section on “Using Helm Charts to Install Apps”.

      Install Istio

      • For Linux or macOS users, use curl to pull the Istio project files. Even though you will use Helm charts to deploy Istio to your cluster, pulling the Istio project files will give you access to the sample Bookinfo application that comes bundled with this installation.

        curl -L https://git.io/getLatestIstio | ISTIO_VERSION=1.3.3 sh -
        
      • If you are using Windows, you will need to go to Istio’s Github repo to find the download. There you will find the latest releases for Windows, Linux, and macOS.

      Note

      Issuing the curl command will create a new directory, istio-1.3.3, in your current working directory. Ensure you move into the directory where you’d like to store your Istio project files before issuing the curl command.

      Install Helm Charts

      1. Add the Istio Helm repo:

        helm repo add istio.io https://storage.googleapis.com/istio-release/releases/1.3.2/charts/
        
      2. Update the helm repo listing:

        helm repo update
        
      3. Verify that you have the repo:

        helm repo list | grep istio.io
        

        The output should be similar to the following:

          
        istio.io	https://storage.googleapis.com/istio-release/releases/1.3.2/charts/
            
        
      4. Install Istio’s Custom Resource Definitions (CRD) with the helm chart. This command also creates a pod namespace called istio-system which you will continue to use for the remainder of this guide.

        helm install --name istio-init --namespace istio-system istio.io/istio-init
        
          
        NAME:   istio-init
        LAST DEPLOYED: Fri Oct 18 10:24:24 2019
        NAMESPACE: istio-system
        STATUS: DEPLOYED
        
        RESOURCES:
        ==> v1/ClusterRole
        NAME                     AGE
        istio-init-istio-system  0s
        
        ==> v1/ClusterRoleBinding
        NAME                                        AGE
        istio-init-admin-role-binding-istio-system  0s
        
        ==> v1/ConfigMap
        NAME          DATA  AGE
        istio-crd-10  1     0s
        istio-crd-11  1     0s
        istio-crd-12  1     0s
        
        ==> v1/Job
        NAME                     COMPLETIONS  DURATION  AGE
        istio-init-crd-10-1.3.2  0/1          0s        0s
        istio-init-crd-11-1.3.2  0/1          0s        0s
        istio-init-crd-12-1.3.2  0/1          0s        0s
        
        ==> v1/Pod(related)
        NAME                           READY  STATUS             RESTARTS  AGE
        istio-init-crd-10-1.3.2-d4gdf  0/1    ContainerCreating  0         0s
        istio-init-crd-11-1.3.2-h8l58  0/1    ContainerCreating  0         0s
        istio-init-crd-12-1.3.2-v9777  0/1    ContainerCreating  0         0s
        
        ==> v1/ServiceAccount
        NAME                        SECRETS  AGE
        istio-init-service-account  1        0s
        
        
      5. Verify that all CRDs were successfully installed:

        kubectl get crds | grep 'istio.io' | wc -l
        

        You should see the following output:

          
        23
        
        

        If the number is less, you may need to wait a few moments for the resources to finish being created.

      6. Install the Helm chart for Istio. There are many installation options available for Istio. For this guide, the command enables Grafana, which you will use later to visualize your cluster’s data.

        helm install --name istio --namespace istio-system istio.io/istio --set grafana.enabled=true
        

        Full output of the Helm chart Istio installation

          
        NAME:   istio
        LAST DEPLOYED: Fri Oct 18 10:28:40 2019
        NAMESPACE: istio-system
        STATUS: DEPLOYED
        
        RESOURCES:
        ==> v1/ClusterRole
        NAME                                     AGE
        istio-citadel-istio-system               43s
        istio-galley-istio-system                43s
        istio-grafana-post-install-istio-system  43s
        istio-mixer-istio-system                 43s
        istio-pilot-istio-system                 43s
        istio-reader                             43s
        istio-sidecar-injector-istio-system      43s
        prometheus-istio-system                  43s
        
        ==> v1/ClusterRoleBinding
        NAME                                                    AGE
        istio-citadel-istio-system                              43s
        istio-galley-admin-role-binding-istio-system            43s
        istio-grafana-post-install-role-binding-istio-system    43s
        istio-mixer-admin-role-binding-istio-system             43s
        istio-multi                                             43s
        istio-pilot-istio-system                                43s
        istio-sidecar-injector-admin-role-binding-istio-system  43s
        prometheus-istio-system                                 43s
        
        ==> v1/ConfigMap
        NAME                                                                DATA  AGE
        istio                                                               2     43s
        istio-galley-configuration                                          1     44s
        istio-grafana                                                       2     43s
        istio-grafana-configuration-dashboards-citadel-dashboard            1     44s
        istio-grafana-configuration-dashboards-galley-dashboard             1     43s
        istio-grafana-configuration-dashboards-istio-mesh-dashboard         1     44s
        istio-grafana-configuration-dashboards-istio-performance-dashboard  1     43s
        istio-grafana-configuration-dashboards-istio-service-dashboard      1     44s
        istio-grafana-configuration-dashboards-istio-workload-dashboard     1     44s
        istio-grafana-configuration-dashboards-mixer-dashboard              1     44s
        istio-grafana-configuration-dashboards-pilot-dashboard              1     44s
        istio-grafana-custom-resources                                      2     44s
        istio-security-custom-resources                                     2     43s
        istio-sidecar-injector                                              2     43s
        prometheus                                                          1     43s
        
        ==> v1/Deployment
        NAME                    READY  UP-TO-DATE  AVAILABLE  AGE
        grafana                 0/1    1           0          42s
        istio-citadel           1/1    1           1          42s
        istio-galley            0/1    1           0          42s
        istio-ingressgateway    0/1    1           0          42s
        istio-pilot             0/1    1           0          42s
        istio-policy            0/1    1           0          42s
        istio-sidecar-injector  0/1    1           0          42s
        istio-telemetry         1/1    1           1          42s
        prometheus              0/1    1           0          42s
        
        ==> v1/Pod(related)
        NAME                                     READY  STATUS             RESTARTS  AGE
        grafana-575c7c4784-ffq79                 0/1    ContainerCreating  0         42s
        istio-citadel-746b4cc66c-2zq2d           1/1    Running            0         42s
        istio-galley-668765c7dc-r7w49            0/1    ContainerCreating  0         42s
        istio-ingressgateway-76ff5cf54b-n5xzl    0/1    Running            0         42s
        istio-pilot-7b6f4b4498-pfcm5             0/2    ContainerCreating  0         42s
        istio-policy-8449665784-xzn7m            0/2    ContainerCreating  0         42s
        istio-sidecar-injector-7488c45bcb-mzfgz  0/1    Running            0         42s
        istio-telemetry-56595ccd89-qxtb7         2/2    Running            1         42s
        prometheus-5679cb4dcd-8fsf4              0/1    ContainerCreating  0         42s
        
        ==> v1/Role
        NAME                      AGE
        istio-ingressgateway-sds  43s
        
        ==> v1/RoleBinding
        NAME                      AGE
        istio-ingressgateway-sds  43s
        
        ==> v1/Service
        NAME                    TYPE          CLUSTER-IP      EXTERNAL-IP      PORT(S)                                                                                                                                     AGE
        grafana                 ClusterIP     10.111.223.85              3000/TCP                                                                                                                                    43s
        istio-citadel           ClusterIP     10.96.57.68                8060/TCP,15014/TCP                                                                                                                          42s
        istio-galley            ClusterIP     10.111.114.219             443/TCP,15014/TCP,9901/TCP                                                                                                                  43s
        istio-ingressgateway    LoadBalancer  10.104.28.12    104.237.148.149  15020:31189/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:30450/TCP,15030:32554/TCP,15031:30659/TCP,15032:32716/TCP,15443:32438/TCP  43s
        istio-pilot             ClusterIP     10.97.46.215               15010/TCP,15011/TCP,8080/TCP,15014/TCP                                                                                                      42s
        istio-policy            ClusterIP     10.104.45.158              9091/TCP,15004/TCP,15014/TCP                                                                                                                42s
        istio-sidecar-injector  ClusterIP     10.110.88.188              443/TCP,15014/TCP                                                                                                                           42s
        istio-telemetry         ClusterIP     10.103.18.40               9091/TCP,15004/TCP,15014/TCP,42422/TCP                                                                                                      42s
        prometheus              ClusterIP     10.105.19.61               9090/TCP                                                                                                                                    42s
        
        ==> v1/ServiceAccount
        NAME                                    SECRETS  AGE
        istio-citadel-service-account           1        43s
        istio-galley-service-account            1        43s
        istio-grafana-post-install-account      1        43s
        istio-ingressgateway-service-account    1        43s
        istio-mixer-service-account             1        43s
        istio-multi                             1        43s
        istio-pilot-service-account             1        43s
        istio-security-post-install-account     1        43s
        istio-sidecar-injector-service-account  1        43s
        prometheus                              1        43s
        
        ==> v1alpha2/attributemanifest
        NAME        AGE
        istioproxy  41s
        kubernetes  41s
        
        ==> v1alpha2/handler
        NAME           AGE
        kubernetesenv  41s
        prometheus     41s
        
        ==> v1alpha2/instance
        NAME                  AGE
        attributes            41s
        requestcount          41s
        requestduration       41s
        requestsize           41s
        responsesize          41s
        tcpbytereceived       41s
        tcpbytesent           41s
        tcpconnectionsclosed  41s
        tcpconnectionsopened  41s
        
        ==> v1alpha2/rule
        NAME                     AGE
        kubeattrgenrulerule      41s
        promhttp                 41s
        promtcp                  41s
        promtcpconnectionclosed  41s
        promtcpconnectionopen    41s
        tcpkubeattrgenrulerule   41s
        
        ==> v1alpha3/DestinationRule
        NAME             AGE
        istio-policy     42s
        istio-telemetry  42s
        
        ==> v1beta1/ClusterRole
        NAME                                      AGE
        istio-security-post-install-istio-system  43s
        
        ==> v1beta1/ClusterRoleBinding
        NAME                                                   AGE
        istio-security-post-install-role-binding-istio-system  43s
        
        ==> v1beta1/MutatingWebhookConfiguration
        NAME                    AGE
        istio-sidecar-injector  41s
        
        ==> v1beta1/PodDisruptionBudget
        NAME                    MIN AVAILABLE  MAX UNAVAILABLE  ALLOWED DISRUPTIONS  AGE
        istio-galley            1              N/A              0                    44s
        istio-ingressgateway    1              N/A              0                    44s
        istio-pilot             1              N/A              0                    44s
        istio-policy            1              N/A              0                    44s
        istio-sidecar-injector  1              N/A              0                    44s
        istio-telemetry         1              N/A              0                    44s
        
        ==> v2beta1/HorizontalPodAutoscaler
        NAME                  REFERENCE                        TARGETS        MINPODS  MAXPODS  REPLICAS  AGE
        istio-ingressgateway  Deployment/istio-ingressgateway  /80%  1        5        1         42s
        istio-pilot           Deployment/istio-pilot           /80%  1        5        1         41s
        istio-policy          Deployment/istio-policy          /80%  1        5        1         42s
        istio-telemetry       Deployment/istio-telemetry       /80%  1        5        1         42s
        
        
        NOTES:
        Thank you for installing Istio.
        
        Your release is named Istio.
        
        To get started running application with Istio, execute the following steps:
        1. Label namespace that application object will be deployed to by the following command (take default namespace as an example)
        
        $ kubectl label namespace default istio-injection=enabled
        $ kubectl get namespace -L istio-injection
        
        2. Deploy your applications
        
        $ kubectl apply -f .yaml
        
        For more information on running Istio, visit:
        https://istio.io/
        
        
        
      7. Verify that the Istio services and Grafana are running:

        kubectl get svc -n istio-system
        

        The output should be similar to the following:

          
        NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                                                                                                                                      AGE
        grafana                  ClusterIP      10.111.81.20              3000/TCP                                                                                                                                     4m6s
        istio-citadel            ClusterIP      10.100.103.171            8060/TCP,15014/TCP                                                                                                                           4m6s
        istio-galley             ClusterIP      10.104.173.105            443/TCP,15014/TCP,9901/TCP                                                                                                                   4m7s
        istio-ingressgateway     LoadBalancer   10.97.218.128    23.92.23.198   15020:30376/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:31358/TCP,15030:30826/TCP,15031:30535/TCP,15032:31728/TCP,15443:31970/TCP   4m6s
        istio-pilot              ClusterIP      10.108.36.63              15010/TCP,15011/TCP,8080/TCP,15014/TCP                                                                                                       4m6s
        istio-policy             ClusterIP      10.111.111.45             9091/TCP,15004/TCP,15014/TCP                                                                                                                 4m6s
        istio-sidecar-injector   ClusterIP      10.96.23.143              443/TCP,15014/TCP                                                                                                                            4m5s
        istio-telemetry          ClusterIP      10.103.224.18             9091/TCP,15004/TCP,15014/TCP,42422/TCP                                                                                                       4m6s
        prometheus               ClusterIP      10.96.246.56              9090/TCP                                                                                                                                     4m6s
        
        
      8. You can also see the pods that are running by using this command:

        kubectl get pods -n istio-system
        

        The output will look similar to this:

          
        NAME                                      READY   STATUS      RESTARTS   AGE
        grafana-575c7c4784-v2vj2                  1/1     Running     0          4m54s
        istio-citadel-746b4cc66c-jnjx9            1/1     Running     0          4m53s
        istio-galley-668765c7dc-vt88j             1/1     Running     0          4m54s
        istio-ingressgateway-76ff5cf54b-dmksf     1/1     Running     0          4m54s
        istio-init-crd-10-1.3.2-t4sqg             0/1     Completed   0          11m
        istio-init-crd-11-1.3.2-glr72             0/1     Completed   0          11m
        istio-init-crd-12-1.3.2-82gn4             0/1     Completed   0          11m
        istio-pilot-7b6f4b4498-vtb8s              2/2     Running     0          4m53s
        istio-policy-8449665784-8hjsw             2/2     Running     4          4m54s
        istio-sidecar-injector-7488c45bcb-b4qz4   1/1     Running     0          4m53s
        istio-telemetry-56595ccd89-jcc9s          2/2     Running     5          4m54s
        prometheus-5679cb4dcd-pbg6m               1/1     Running     0          4m53s
        
        
      9. Before moving on, be sure that all pods are in the Running or Completed status.

        Note

        If you need to troubleshoot, you can check a specific pod by using kubectl, remembering that you set the namespace to istio-system:

        kubectl describe pods pod_name -n pod_namespace
        

        And check the logs by using:

        kubectl logs pod_name -n pod_namespace
        

      Set up Envoy Proxies

      1. Istio’s service mesh runs by employing sidecar proxies. You will enable them by injecting them into the containers. This command is using the default namespace which is where you will be deploying the Bookinfo application.

        kubectl label namespace default istio-injection=enabled
        

        Note

        This deployment uses automatic sidecar injection. Automatic injection can be disabled and manual injection enabled during installation via istioctl. If you disabled automatic injection during installation, use the following command to modify the bookinfo.yaml file before deploying the application:

        kubectl apply -f <(istioctl kube-inject -f ~/istio-1.3.3/samples/bookinfo/platform/kube/bookinfo.yaml)
        
      2. Verify that the ISTIO-INJECTION was enabled for the default namespace:

        kubectl get namespace -L istio-injection
        

        You will get a similar output:

          
        NAME           STATUS   AGE    ISTIO-INJECTION
        default        Active   101m   enabled
        istio-system   Active   37m
        kube-public    Active   101m
        kube-system    Active   101m
        
        

      Install the Istio Bookinfo App

      The Bookinfo app is a sample application that comes packaged with Istio. It features four microservices in four different languages that are all separate from Istio itself. The application is a simple single page website that displays a “book store” catalog page with one book, it’s details, and some reviews. The microservices are:

      • productpage is written in Python and calls details and reviews to populate the page.
      • details is written in Ruby and contains the book information.
      • reviews is written in Java and contains book reviews and calls ratings.
      • ratings is written in Node.js and contains book ratings. There are three versions of this microservice in the application. A different version is called each time the page is refreshed.
      1. Navigate to the directory where you installed Istio.

      2. The bookinfo.yaml file is the application manifest. It specifies all the service and deployment objects for the application. Here is just the productpage section of this file; feel free to browse the entire file:

        ~/istio-1.3.3/samples/bookinfo/platform/kube/bookinfo.yaml
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        
        ...
        
        apiVersion: v1
        kind: Service
        metadata:
          name: productpage
          labels:
            app: productpage
            service: productpage
        spec:
          ports:
          - port: 9080
            name: http
          selector:
            app: productpage
        ---
        apiVersion: v1
        kind: ServiceAccount
        metadata:
          name: bookinfo-productpage
        ---
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: productpage-v1
          labels:
            app: productpage
            version: v1
        spec:
          replicas: 1
          selector:
            matchLabels:
              app: productpage
              version: v1
          template:
            metadata:
              labels:
                app: productpage
                version: v1
            spec:
              serviceAccountName: bookinfo-productpage
              containers:
              - name: productpage
                image: docker.io/istio/examples-bookinfo-productpage-v1:1.15.0
                imagePullPolicy: IfNotPresent
                ports:
                - containerPort: 9080
        ---
      3. Start the Bookinginfo application with the following command:

        kubectl apply -f ~/istio-1.3.3/samples/bookinfo/platform/kube/bookinfo.yaml
        

        The following output results:

          
        service/details created
        serviceaccount/bookinfo-details created
        deployment.apps/details-v1 created
        service/ratings created
        serviceaccount/bookinfo-ratings created
        deployment.apps/ratings-v1 created
        service/reviews created
        serviceaccount/bookinfo-reviews created
        deployment.apps/reviews-v1 created
        jdeployment.apps/reviews-v2 created
        deployment.apps/reviews-v3 created
        service/productpage created
        serviceaccount/bookinfo-productpage created
        deployment.apps/productpage-v1 created
        
        
      4. Check that all the services are up and running:

        kubectl get services
        

        The output will look similar to the following:

          
            NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
        details       ClusterIP   10.97.188.175           9080/TCP   3m
        kubernetes    ClusterIP   10.96.0.1               443/TCP    154m
        productpage   ClusterIP   10.110.184.42           9080/TCP   2m37s
        ratings       ClusterIP   10.102.206.99           9080/TCP   2m59s
        reviews       ClusterIP   10.106.21.117           9080/TCP   2m59s
        
        
      5. Check that the pods are all up:

        kubectl get pods
        

        The expected output should look similar, with all pods running:

          
        NAME                              READY   STATUS    RESTARTS   AGE
        details-v1-68fbb76fc-qfpbd        2/2     Running   0          4m48s
        productpage-v1-6c6c87ffff-th52x   2/2     Running   0          4m15s
        ratings-v1-7bdfd65ccc-z8grs       2/2     Running   0          4m48s
        reviews-v1-5c5b7b9f8d-6xljj       2/2     Running   0          4m41s
        reviews-v2-569796655b-x2n4v       2/2     Running   0          4m30s
        reviews-v3-844bc59d88-pwl6b       2/2     Running   0          4m30s
        
        

        Note

        If you do not see all pods running right away, you may need to wait a few moments for them to complete the initialization process.

      6. Check that the Bookinfo application is running. This command will pull the title tag and contents from the /productpage running on the ratings pod:

        kubectl exec -it $(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}') -c ratings -- curl productpage:9080/productpage | grep -o "<title>.*</title>"
        

        The expected output will look like this:

          
        <title>Simple Bookstore App</title>
        
        

      Open the Istio Gateway

      When checking the services in the previous section, you may have noticed none had external IPs. This is because Kubernetes services are private by default. You will need to open a gateway in order to access the app from the web browser. To do this you will use an Istio Gateway.

      Here are the contents of the bookinfo-gateway.yaml file that you will use to open the gateway:

      ~/istio-1.3.3/samples/bookinfo/networking/bookinfo-gateway.yaml
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      
      apiVersion: networking.istio.io/v1alpha3
      kind: Gateway
      metadata:
        name: bookinfo-gateway
      spec:
        selector:
          istio: ingressgateway # use istio default controller
        servers:
        - port:
            number: 80
            name: http
            protocol: HTTP
          hosts:
          - "*"
      ---
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: bookinfo
      spec:
        hosts:
        - "*"
        gateways:
        - bookinfo-gateway
        http:
        - match:
          - uri:
              exact: /productpage
          - uri:
              prefix: /static
          - uri:
              exact: /login
          - uri:
              exact: /logout
          - uri:
              prefix: /api/v1/products
          route:
          - destination:
              host: productpage
              port:
                number: 9080
      • The Gateway section sets up the server and specifies the port and protocol that will be opened through the gateway. Note that the name must match Istio’s named service ports standardization scheme.
      • In the Virtual Service section, the http field defines how HTTP traffic will be routed, and the destination field says where requests are routed.
      1. Apply the ingress gateway with the following command:

        kubectl apply -f ~/istio-1.3.3/samples/bookinfo/networking/bookinfo-gateway.yaml
        

        You should see the following output:

          
        gateway.networking.istio.io/bookinfo-gateway created
        virtualservice.networking.istio.io/bookinfo created
        
        
      2. Confirm that the gateway is open:

        kubectl get gateway
        

        You should see the following output:

          
        NAME               AGE
        bookinfo-gateway   1m
        
        
      3. Access your ingress gateway’s external IP. This IP will correspond to the value listed under EXTERNAL-IP.

        kubectl get svc istio-ingressgateway -n istio-system
        

        The output should resemble the following. In the example, the external IP is 192.0.2.0. You will need this IP address in the next section to access your Bookinfo app.

          
        NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                                                                                                                                      AGE
        istio-ingressgateway   LoadBalancer   10.97.218.128   192.0.2.0   15020:30376/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:31358/TCP,15030:30826/TCP,15031:30535/TCP,15032:31728/TCP,15443:31970/TCP   21h
        
        

      Apply Default Destination Rules

      Destination rules specify named service subsets and give them routing rules to control traffic to the different instances of your services.

      1. Apply destination rules to your cluster:

        kubectl apply -f ~/istio-1.3.3/samples/bookinfo/networking/destination-rule-all.yaml
        

        The output will appear as follows:

          
        destinationrule.networking.istio.io/productpage created
        destinationrule.networking.istio.io/reviews created
        destinationrule.networking.istio.io/ratings created
        destinationrule.networking.istio.io/details created
        
        
      2. To view all the applied rules issue the following command:

        kubectl get destinationrules -o yaml
        

      Visualizations with Grafana

      1. In a new terminal window that you can leave running, open the port for Grafana:

        kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') 3000:3000 &
        
      2. Create an SSH tunnel from your local machine to your Linode so that you can access the localhost of your Linode, entering your credentials as prompted:

        ssh -L 3000:localhost:3000 <username>@<ipaddress>
        

        Once this is completed, visit the following URL in your web browser to access your Mesh Dashboard:

        http://localhost:3000/dashboard/db/istio-mesh-dashboard
        

        Note

        In this example, you will use an SSH tunnel to access your cluster’s running Grafana service. You could set up an ingress gateway for your Grafana service in the same way you did for the Bookinfo app. Those steps are not covered in this guide.

      3. You will see the Mesh Dashboard. There will be no data available yet.

        Istio Dashboard

      4. Send data by visiting a product page, replacing 192.0.2.0 with the value for your ingress gateway’s external IP:

        http://192.0.2.0/productpage
        

        Refresh the page a few times to generate some traffic.

      5. Return to the dashboard and refresh the page to see the data.

        Istio Dashboard Refreshed

        The Mesh Dashboard displays a general overview of Istio service mesh, the services that are running, and their workloads.

      6. To view a specific service or workload you can click on them from the HTTP/GRPC Workloads list. Under the Service column, click productpage.default.svc.cluster.local from the HTTP/GRPC Workloads list.

        Istio Service List Mesh Dashboard

      7. This will open a Service dashboard specific to this service.

        Istio Product Service Detail Dashboard

      8. Feel free to explore the other Grafana dashboards for more metrics and data. You can access all the dashboards from the dropdown menu at the top left of the screen.

      Removing Clusters and Deployments

      If you at any time need to remove the resources created when following this guide, enter the following commands, confirming any prompts that appear:

      helm delete istio-init
      helm delete istio
      linode-cli k8s-alpha delete istio-cluster
      

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      How to Use the Linode Packer Builder


      Updated by Linode Contributed by Linode

      What is Packer?

      Packer is a HashiCorp maintained open source tool that is used to create machine images. A machine image provides the operating system, applications, application configurations, and data files that a virtual machine instance will run once it’s deployed. Using a single source configuration, you can generate identical machine images. Packer can be used in conjunction with common configuration management tools like Chef, Puppet, or Ansible to install software to your Linode and include those configurations into your image.

      In this guide you will complete the following steps:

      Before You Begin

      1. Ensure you have access to cURL on your computer.

      2. Generate a Linode API v4 access token with permission to read and write Linodes. You can follow the Get an Access Token section of the Getting Started with the Linode API guide if you do not already have one.

        Note

        The example cURL commands in this guide will refer to a $TOKEN environment variable. For example:

        curl -H "Authorization: Bearer $TOKEN" 
            https://api.linode.com/v4/images
        

        To set this variable up in your terminal, run:

        export TOKEN='<your-Linode-APIv4-token>'
        

        If you do not do this, you will need to alter these commands so that your API token is inserted wherever $TOKEN appears.

      3. Create an SSH authentication key-pair if your computer does not already have one. Your SSH public key will be added to your image via an Ansible module.

      4. Install Ansible on your computer and familiarize yourself with basic Ansible concepts (optional). Using the Getting Started With Ansible – Basic Installation and Setup guide, follow the steps in the Install Ansible section.

      The Linode Packer Builder

      In Packer’s ecosystem, builders are responsible for deploying machine instances and generating redeployable images from them. The Linode Packer builder can be used to create a Linode image that can be redeployed to other Linodes. You can share your image template across your team to ensure everyone is using a uniform development and testing environment. This process will help your team maintain an immutable infrastructure within your continuous delivery pipeline.

      The Linode Packer builder works in the following way:

      • You create a template to define the type of image you want Packer to build.
      • Packer uses the template to build the image on a temporary Linode.
      • A snapshot of the built image is taken and stored as a private Linode image.
      • The temporary Linode is deleted.
      • You can then reuse the private Linode image as desired, for example, by using your image to create Linode instances with Terraform.

      Install Packer

      The following instructions will install Packer on Ubuntu 18.04 from a downloaded binary. For more installation methods, including installing on other operating systems or compiling from source, see Packer’s official documentation.

      1. Make a Packer project directory in your home directory and then navigate to it:

        mkdir ~/packer
        cd ~/packer
        
      2. Download the precompiled binary for your system from the Packer website. Example wget commands are listed using the latest version available at time of publishing (1.4.4). You should inspect the links on the download page to see if a newer version is available and update the wget commands to use those URLs instead:

        • The 64-bit Linux .zip archive

          wget https://releases.hashicorp.com/packer/1.4.4/packer_1.4.4_linux_amd64.zip
          
        • The SHA256 checksums file

          wget https://releases.hashicorp.com/packer/1.4.4/packer_1.4.4_SHA256SUMS
          
        • The checksum signature file

          wget https://releases.hashicorp.com/packer/1.4.4/packer_1.4.4_SHA256SUMS.sig
          

      Verify the Download

      1. Import the HashiCorp Security GPG key (listed on the HashiCorp Security page under Secure Communications):

        gpg --recv-keys 51852D87348FFC4C
        

        The output should show that the key was imported:

          
        gpg: keybox '/home/user/.gnupg/pubring.kbx' created
        gpg: key 51852D87348FFC4C: 17 signatures not checked due to missing keys
        gpg: /home/user/.gnupg/trustdb.gpg: trustdb created
        gpg: key 51852D87348FFC4C: public key "HashiCorp Security " imported
        gpg: no ultimately trusted keys found
        gpg: Total number processed: 1
        gpg:               imported: 1
        
        
      2. Verify the checksum file’s GPG signature:

        gpg --verify packer*.sig packer*SHA256SUMS
        

        The output should contain the Good signature from "HashiCorp Security <[email protected]>" confirmation message:

          
        gpg: Signature made Tue 01 Oct 2019 06:30:17 PM UTC
        gpg:                using RSA key 91A6E7F85D05C65630BEF18951852D87348FFC4C
        gpg: Good signature from "HashiCorp Security " [unknown]
        gpg: WARNING: This key is not certified with a trusted signature!
        gpg:          There is no indication that the signature belongs to the owner.
        Primary key fingerprint: 91A6 E7F8 5D05 C656 30BE  F189 5185 2D87 348F FC4C
              
        
      3. Verify that the fingerprint output matches the fingerprint listed in the Secure Communications section of the HashiCorp Security page.

      4. Verify the .zip archive’s checksum:

        sha256sum -c packer*SHA256SUMS 2>&1 | grep OK
        

        The output should show the file’s name as given in the packer*SHA256SUMS file:

          
        packer_1.4.4_linux_amd64.zip: OK
              
        

      Configure the Packer Environment

      1. Unzip packer_*_linux_amd64.zip to your ~/packer directory:

        unzip packer_*_linux_amd64.zip
        

        Note

        If you receive an error that indicates unzip is missing from your system, install the unzip package and try again.

      2. Edit your ~./profile shell configuration file to include the ~/packer directory in your PATH. Then, reload the Bash profile:

        echo 'export PATH="$PATH:$HOME/packer"' >> ~/.profile
        source ~/.profile
        

        Note

        If you use a different shell, your shell configuration may have a different file name.

      3. Verify Packer can run by calling it with no options or arguments:

        packer
        
          
        Usage: packer [--version] [--help]  []
        
        Available commands are:
            build       build image(s) from template
            console     creates a console for testing variable interpolation
            fix         fixes templates from old versions of packer
            inspect     see components of a template
            validate    check that a template is valid
            version     Prints the Packer version
            
        

      Use the Linode Packer Builder

      Now that Packer is installed on your local system, you can create a Packer template. A template is a JSON formatted file that contains the configurations needed to build a machine image.

      In this section you will create a template that uses the Linode Packer builder to create an image using Debian 9 as its base distribution. The template will also configure your system image with a new limited user account, and a public SSH key from your local computer. The additional system configuration will be completed using Packer’s Ansible provisioner and an example Ansible Playbook. A Packer provisioner is a built-in third-party integration that further configures a machine instance during the boot process and prior to taking the machine’s snapshot.

      Note

      The steps in this section will incur charges related to deploying a 1GB Nanode. The Linode will only be deployed for the duration of the time needed to create and snapshot your image and will then be deleted. See our Billing and Payments guide for details about hourly billing.

      Access Linode and Private Images

      The Linode Packer Builder requires a Linode Image ID to deploy a disk from. This guide’s example will use the image linode/debian9, but you can replace it with any other image you prefer. To list the official Linode images and your account’s private images, you can curl the Linode API:

      curl -H "Authorization: Bearer $TOKEN" 
          https://api.linode.com/v4/images
      

      Create Your Template

      Note

      The Packer builder does not manage images. Once it creates an image, it will be stored on your Linode account and can be accessed and used as needed from the Linode Cloud Manager, via Linode’s API v4, or using third-party tools like Terraform. Linode Images are limited to 2GB per Image and 3 Images per account.

      Create a file named example.json with the following content:

      ~/packer/example.json
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
      {
        "variables": {
          "my_linode_token": ""
        },
        "builders": [{
          "type": "linode",
          "image": "linode/debian9",
          "linode_token": "{{user `my_linode_token` }}",
          "region": "us-east",
          "instance_type": "g6-nanode-1",
          "instance_label": "temp-linode-packer",
          "image_label": "my-private-packer-image",
          "image_description": "My private packer image",
          "ssh_username": "root"
        }],
        "provisioners": [
          {
            "type": "ansible",
            "playbook_file": "./limited_user_account.yml"
          }
        ]
      }

      If you would rather not use a provisioner in your Packer template, you can use the example file below:

      ~/packer/example.json
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      
      {
        "variables": {
          "my_linode_token": ""
        },
        "builders": [{
          "type": "linode",
          "image": "linode/debian9",
          "linode_token": "{{user `my_linode_token` }}",
          "region": "us-east",
          "instance_type": "g6-nanode-1",
          "instance_label": "temp-linode-packer",
          "image_label": "my-private-packer-image",
          "image_description": "My private packer image",
          "ssh_username": "root"
        }]
      }

      There are three sections to the Packer template file:

      • variables: This section allows you to further configure your template with command-line variables, environment variables, Vault, or variable files. In the section that follows, you will use a command line variable to pass your Linode account’s API token to the template.
      • builders: The builder section contains the definition for the machine image that will be created. In the example template, you use a single builder –the Linode builder. The builder uses the linode/debian9 image as its base and will assign the image a label of my-private-packer-image. It will deploy a 1GB Nanode, take a snapshot, and create a reusable Linode Image. Refer to Packer’s official documentation for a complete Linode Builder configuration reference.

        Note

        You can use multiple builders in a single template file. This process is known as a parallel build which allows you to create multiple images for multiple platforms from a single template.
      • provisioners: (optional) with a provisioner you can further configure your system by completing common system administration tasks, like adding users, installing and configuring software, and more. The example uses Packer’s built-in Ansible provider and executes the tasks defined in the local limited_user_account.yml playbook. This means your Linode image will also contain anything executed by the playbook on your Nanode. Packer supports several other provisioners, like Chef, Salt, and shell scripts.

      Create your Ansible Playbook (Optional)

      In the previous section you created a Packer template that makes use of an Ansible Playbook to add system configurations to your image. Prior to building your image, you will need to create the referenced limited_user_account.yml Playbook. You will complete those steps in this section. If you chose not to use the Ansible provider, you can skip this section.

      1. The example Ansible Playbook makes use of Ansible’s user module. This module requires that a hashed value be used for its password parameter. Use the mkpasswd utility to generate a hashed password that you will use in the next step.

        mkpasswd --method=sha-512
        

        You will be prompted to enter a plain-text password and the utility will return a hash of the password.

          
        Password:
        $6$aISRzCJH4$nNJ/9ywhnH/raHuVCRu/unE7lX.L9ragpWgvD0rknlkbAw0pkLAwkZqlY.ahjj/AAIKo071LUB0BONl.YMsbb0
                  
        
      2. In your packer directory, create a file with the following content. Ensure you replace the value of the password parameter with your own hashed password:

        ~/packer/limited_user_account.yml
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        
        ---
        - hosts: all
          remote_user: root
          vars:
            NORMAL_USER_NAME: 'my-user-name'
          tasks:
            - name: "Create a secondary, non-root user"
              user: name={{ NORMAL_USER_NAME }}
                    password='$6$eebkauNy4h$peyyL1MTN7F4JKG44R27TTmbXlloDUsjPir/ATJue2bL0u8FBk0VuUvrpsMq6rSSOCm8VSip0QHN8bDaD/M/k/'
                    shell=/bin/bash
            - name: Add remote authorized key to allow future passwordless logins
              authorized_key: user={{ NORMAL_USER_NAME }} key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
            - name: Add normal user to sudoers
              lineinfile: dest=/etc/sudoers
                          regexp="{{ NORMAL_USER_NAME }} ALL"
                          line="{{ NORMAL_USER_NAME }}"
        • This Playbook will created a limited user account named my-user-name. You can replace my-user-name, the value of the variable NORMAL_USER_NAME, with any system username you’d like to create. It will then add a public SSH key stored on your local computer. If the public key you’d like to use is stored in a location other than ~/.ssh/id_rsa.pub, you can update that value. Finally, the Playbook adds the new system user to the sudoers file.

      Create your Linode Image

      You should now have your completed template file and your Ansible Playbook file (optional) and can validate the template and finally, build your image.

      1. Validate the template before building your image. Replace the value of my_linode_token with your own Linode API v4 token.

        packer validate -var 'my_linode_token=myL0ngT0kenStr1ng' example.json
        

        If successful, you will see the following:

          
        Template validated successfully.
              
        

        Note

        To learn how to securely store and use your API v4 token, see the Vault Variables section of Packer’s documentation.
      2. You can now build your final image. Replace the value of my_linode_token with your own Linode API v4 token. This process may take a few minutes to complete.

        packer build -var 'my_linode_token=myL0ngT0kenStr1ng' example.json
        
          
        linode output will be in this color.
        
        ==> linode: Running builder ...
        ==> linode: Creating temporary SSH key for instance...
        ==> linode: Creating Linode...
        ==> linode: Using ssh communicator to connect: 192.0.2.0
        ==> linode: Waiting for SSH to become available...
        ==> linode: Connected to SSH!
        ==> linode: Provisioning with Ansible...
        ==> linode: Executing Ansible: ansible-playbook --extra-vars packer_build_name=linode packer_builder_type=linode -o IdentitiesOnly=yes -i /tmp/packer-provisioner-ansible136766862 /home/user/packer/limited_user_account.yml -e ansible_ssh_private_key_file=/tmp/ansible-key642969643
            linode:
            linode: PLAY [all] *********************************************************************
            linode:
            linode: TASK [Gathering Facts] *********************************************************
            linode: ok: [default]
            linode:
            linode: TASK [Create a secondary, non-root user] ***************************************
            linode: changed: [default]
            linode:
            linode: TASK [Add remote authorized key to allow future passwordless logins] ***********
            linode: changed: [default]
            linode:
            linode: TASK [Add normal user to sudoers] **********************************************
            linode: changed: [default]
            linode:
            linode: PLAY RECAP *********************************************************************
            linode: default                    : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
            linode:
        ==> linode: Shutting down Linode...
        ==> linode: Creating image...
        Build 'linode' finished.
        
        ==> Builds finished. The artifacts of successful builds are:
        --> linode: Linode image: my-private-packer-image (private/7550080)
              
        

        The output will provide you with your new private image’s ID. In the example output the image ID is private/7550080. This image is now available on your Linode account to use as you desire. As an example, in the next section you will use this newly created image to deploy a new 1 GB Nanode using Linode’s API v4.

      Deploy a Linode with your New Image

      1. Issue the following curl command to deploy a 1GB Nanode to the us-east data center using your new Image to your Linode account. Ensure you replace private/7550080 with your own Linode Image’s ID and assign your own root_pass and label.

        curl -H "Content-Type: application/json" 
          -H "Authorization: Bearer $TOKEN" 
          -X POST -d '{
            "image": "private/7550080",
            "root_pass": "[email protected]",
            "booted": true,
            "label": "my-example-label",
            "type": "g6-nanode-1",
            "region": "us-east"
          }' 
          https://api.linode.com/v4/linode/instances
        

        You should receive a similar response from the API:

          
        {"id": 17882092, "created": "2019-10-23T22:47:47", "group": "", "specs": {"gpus": 0, "transfer": 1000, "memory": 1024, "disk": 25600, "vcpus": 1}, "label": "my-example-linode", "updated": "2019-10-23T22:47:47", "watchdog_enabled": true, "image": null, "ipv4": ["192.0.2.0"], "ipv6": "2600:3c03::f03c:92ff:fe98:6d9a/64", "status": "provisioning", "tags": [], "region": "us-east", "backups": {"enabled": false, "schedule": {"window": null, "day": null}}, "hypervisor": "kvm", "type": "g6-nanode-1", "alerts": {"cpu": 90, "network_in": 10, "transfer_quota": 80, "io": 10000, "network_out": 10}}%
            
        
      2. If you used the Ansible provisioner, once your Linode is deployed, you should be able to SSH into your newly deployed Linode using the limited user account you created with the Ansible playbook and your public SSH key. Your Linode’s IPv4 address will be available in the API response returned after creating the Linode.

        ssh [email protected]
        

      Next Steps

      If you’d like to learn how to use Terraform to deploy Linodes using your Packer created image, you can follow our Terraform guides to get started:

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      Learning Go Functions, Loops, and Errors – A Tutorial


      Updated by Linode Contributed by Mihalis Tsoukalos

      Introduction

      Go is a modern, open source, and general-purpose programming language that began as an internal Google project and was officially announced at the end of 2009. Go was inspired by many other programming languages including C, Pascal, Alef, and Oberon. Its spiritual fathers were Robert Griesemer, Ken Thomson, and Rob Pike, who all designed Go as a language for professional programmers that want to build reliable, robust, and efficient software. Apart from its syntax and its standard functions, Go comes with a rich standard library.

      In this Guide

      This guide will cover the following topics:

      Note

      This guide was written with Go version 1.13.

      Before You Begin

      1. You will need Go installed on your computer. To get it, go to Go’s official download page and get the installer for your operating system, or you can install it from source. Follow the installation instructions for your operating system.

      2. Add /usr/local/go/bin to the PATH environment variable:

        export PATH=$PATH:/usr/local/go/bin
        

        You may need to restart your shell for this change to apply.

      The Advantages of Go

      Although Go is not perfect, it has many advantages, including the following:

      • It is a modern programming language that was made by experienced developers for developers.
      • The code is easy to read.
      • Go keeps concepts orthogonal, or simple, because a few orthogonal features work better than many overlapping ones.
      • The compiler prints practical warnings and error messages that help you solve the actual problem.
      • It has support for procedural, concurrent, and distributed programming.
      • Go supports garbage collection so you do not have to deal with memory allocation and deallocation.
      • Go can be used to build web applications and it provides a simple web server for testing purposes.
      • The standard Go library offers many packages that simplify the work of the developer.
      • It uses static linking by default, which means that the produced binary files can be easily transferred to other machines with the same OS and architecture. As a consequence, once a Go program is compiled successfully and the executable file is generated, the developer does not need to worry about dependencies and library versions.
      • The code is portable, especially among UNIX machines.
      • Go can be used for writing UNIX systems software.
      • It supports Unicode by default which means that you do not need any extra code for printing characters from multiple human languages or symbols.

      Executing Go code

      There are two kinds of Go programs: autonomous programs that are executable, and Go libraries. Go does not care about an autonomous program’s file name. What matters is that the package name is main and that there is a single main() function in it. This is because the main() function is where program execution begins. As a result, you cannot have multiple main() functions in the files of a single project.

      A Simple Go program

      This is the Go version of the Hello World program:

      ./helloworld.go
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      package main
      
      import (
          "fmt"
      )
      
      func main() {
          fmt.Println("Hello World!")
      }
      • All Go code is delivered within Go packages. For executable programs, the package name should be main. Package declarations begin with the package keyword.

      • Executable programs should have a function named main() without any function parameters. Function definitions begin with the func keyword.

      • Go packages might include import statements for importing Go packages. However, Go demands that you use some functionality from each one of the packages that you import. There is a way to bypass this rule, however, it is considered a bad practice to do this.

        The helloworld.go file above imports the fmt package and uses the fmt.Println() function from that package.

        Note

        All exported package functions begin with an uppercase letter. This follows the Go rule: if you export something outside the current package, it should begin with an uppercase letter. This rule applies even if the field of the Go structure or the global variable is included in a Go package.

      • Go statements do not need to end with a semicolon. However, you are free to use semicolons if you wish. For more information on formatting with curly braces, see the section below.

      1. Now that you better understand the helloworld.go program, execute it with the go run command:

        go run helloworld.go
        

        You will see the following output:

          
        Hello World!
        
        

        This is the simplest of two ways that you can execute Go code. The go run command compiles the code and creates a temporary executable file that is automatically executed and then it deletes that temporary executable file. This is similar to using a scripting programming language.

      2. The second method to execute Go code is to use the build command. Run the following command to use this method:

        go build helloworld.go
        

        The result of that command is a binary executable file that you have to manually execute. This method is similar to the way you execute C code on a UNIX machine. The executable file is named after the Go source filename, which means that in this case the result will be an executable file named helloworld. Go creates statically linked executable files that have no dependencies to external libraries.

      3. Execute the helloworld file:

        ./helloworld
        

        You will see the following output:

          
        Hello World!
        
        

        Note

        The go run command is usually used while experimenting and developing new Go projects. However, if you need to transfer an executable file to another system with the same architecture, you should use go build.

      Formatting Curly Braces

      The following version of the “Hello World” program will not compile:

      ./curly.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      package main
      
      import (
          "fmt"
      )
      
      func main()
      {
          fmt.Println("Hello World!")
      }
      1. Execute the program above, and observer the error message generated by the compiler:

        go run curly.go
        
          
        # command-line-arguments
        ./curly.go:7:6: missing function body
        ./curly.go:8:1: syntax error: unexpected semicolon or newline before {
        
        
      • This error message is generated because Go requires the use of semicolons as statement terminators in many contexts and the compiler automatically inserts the required semicolons when it thinks that they are necessary. Putting the opening curly brace ({) on its own line makes the Go compiler look for a semicolon at the end of the previous line (func main()), which is the cause of the error message.

      • There is only one way to format curly braces in Go; the opening curly brace must not appear on it’s own line. Additionally, you must use curly braces even if a code block contains a single Go statement, like in the body of a for loop. You can see an example of this in the first version of the helloworld.go program or in the Loops in Go section.

      The Assignment Operator and Short Variable Declarations

      • Go supports assignment (=) operators and short variable declarations (:=).
      • With := you can declare a variable and assign a value to it at the same time. The type of the variable is inferred from the given value.
      • You can use = in two cases. First, to assign a new value to an existing variable and second, to declare a new variable, provided that you also give its type.

        For example, var aVariable int = 10, is equivalent to aVariable := 10 assuming aVariable is an int.

      • When you specifically want to control a variable’s type, it is safer to declare the variable and its type using var and then assign a value to it using =.

      Loops in Go

      The file loops.go demonstrates loops in Go:

      ./loops.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      
      package main
      
      import (
          "fmt"
      )
      
      func main() {
          for loopIndex := 0; loopIndex < 20; loopIndex++ {
              if loopIndex%10 == 0 {
                  continue
              }
      
              if loopIndex == 19 {
                  break
              }
              fmt.Print(loopIndex, " ")
          }
          fmt.Println()
      
          // Use break to exit the for loop
          loopIndex := 10
          for {
              if loopIndex < 0 {
                  break
              }
              fmt.Print(loopIndex, " ")
              loopIndex--
          }
          fmt.Println()
      
          // This is similar to a while(true) do something loop
          loopIndex = 0
          anExpression := true
          for ok := true; ok; ok = anExpression {
              if loopIndex > 10 {
                  anExpression = false
              }
      
              fmt.Print(loopIndex, " ")
              loopIndex++
          }
          fmt.Println()
      
          anArray := [5]int{0, 1, -1, 2, -2}
          for loopIndex, value := range anArray {
              fmt.Println("index:", loopIndex, "value: ", value)
          }
      }
      • There are two types of for loops in Go. Traditional for loops that use a control variable initialization, condition, and afterthought; and those that iterate over the elements of a Go data type such as an array or a map using the range keyword.

      • Go has no direct support for while loops. If you want to use a while loop, you can emulate it with a for loop.

      • In their simplest form, for loops allow you to iterate, a predefined number of times, for as long as a condition is valid, or according to a value that is calculated at the beginning of the for loop. Such values include the size of a slice or an array, or the number of keys on a map. However, range is more often used for accessing all the elements of a slice, an array, or a map because you do not need to know the object’s cardinality in order to process its elements one by one. For simplicity, this example uses an array, and a later example will use a slice.

      • You can completely exit a for loop using the break keyword. The break keyword also allows you to create a for loop without an exit condition because the exit condition can be included in the code block of the for loop. You are also allowed to have multiple exit conditions in a for loop.

      • You can skip a single iteration of a for loop using the continue keyword.

      1. Execute the loops.go program:

        go run loops.go
        

        You will see the following output:

          
        1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 18
        10 9 8 7 6 5 4 3 2 1 0
        0 1 2 3 4 5 6 7 8 9 10 11
        index: 0 value:  0
        index: 1 value:  1
        index: 2 value:  -1
        index: 3 value:  2
        index: 4 value:  -2
            
        

      Functions in Go

      Functions are first class citizens in Go, which means that functions can be parameters to other functions as well as returned by functions. This section will illustrate various types of functions.

      Go also supports anonymous functions. These can be defined inline without the need for a name and they are usually used for implementing operations that require a small amount of code. In Go, a function can return an anonymous function or take an anonymous function as one of its arguments. Additionally, anonymous functions can be attached to Go variables. In functional programming terminology anonymous functions are called closures. It is considered a good practice for anonymous functions to have a small implementation and a local focus.

      Regular functions

      This section will present the implementation of some traditional functions.

      ./functions.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      
      package main
      
      import (
          "fmt"
      )
      
      func doubleSquare(firstNum int) (int, int) {
          return firstNum * 2, firstNum * firstNum
      }
      
      func namedMinMax(firstNum, secondNum int) (min, max int) {
          if firstNum > secondNum {
              min = secondNum
              max = firstNum
          } else {
              min = firstNum
              max = secondNum
          }
          return
      }
      
      func minMax(firstNum, secondNum int) (min, max int) {
          if firstNum > secondNum {
              min = secondNum
              max = firstNum
          } else {
              min = firstNum
              max = secondNum
          }
          return min, max
      }
      
      func main() {
          secondNum := 10
      
          square := func(numberToSquare int) int {
              return numberToSquare * numberToSquare
          }
          fmt.Println("The square of", secondNum, "is", square(secondNum))
      
          double := func(numberToDouble int) int {
              return numberToDouble + numberToDouble
          }
          fmt.Println("The double of", secondNum, "is", double(secondNum))
      
          fmt.Println(doubleSquare(secondNum))
          doubledNumber, squaredNumber := doubleSquare(secondNum)
          fmt.Println(doubledNumber, squaredNumber)
      
          value1 := -10
          value2 := -1
          fmt.Println(minMax(value1, value2))
          min, max := minMax(value1, value2)
          fmt.Println(min, max)
          fmt.Println(namedMinMax(value1, value2))
          min, max = namedMinMax(value1, value2)
          fmt.Println(min, max)
      }
      • The main() function takes no arguments and returns no arguments. Once the special function main() exits, the program automatically ends.

      • The doubleSquare() function requires a single int parameter and returns two int values, which is defined as (int, int).

      • All function arguments must have a name – variadic functions are the only exception to this rule.

      • If a function returns a single value, you do not need to put parenthesis around its type.

      • Because namedMinMax() has named return values in its signature, the min and max parameters are automatically returned in the order in which they were put in the function definition. Therefore, the function does not need to explicitly return any variables or values in its return statement at the end, and does not. minMax() function has the same functionality as namedMinMax() but it explicitly returns its values demonstrating that both ways are valid.

      • Both square and double variables in main() are assigned an anonymous function. However, nothing stops you from changing the value of square, double, or any other variable that holds the result of an anonymous function, afterwards. This means that both variables may have a different value in the future.

      1. Execute the functions.go program.

        go run functions.go
        

        Your output will resemble the following:

          
        The square of 10 is 100
        The double of 10 is 20
        20 100
        20 100
        -10 -1
        -10 -1
        -10 -1
        -10 -1
            
        

      Variadic functions

      Variadic functions are functions that accept a variable number of arguments. The most popular variadic functions in Go can be found in the fmt package. The code of variadic.go illustrates the creation and the use of variadic functions.

      ./variadic.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      
      package main
      
      import (
          "fmt"
      )
      
      func varFunc(input ...string) {
          fmt.Println(input)
      }
      
      func oneByOne(message string, sliceOfNumbers ...int) int {
          fmt.Println(message)
          sum := 0
          for indexInSlice, sliceElement := range sliceOfNumbers {
              fmt.Print(indexInSlice, sliceElement, "t")
              sum = sum + sliceElement
          }
          fmt.Println()
          sliceOfNumbers[0] = -1000
          return sum
      }
      
      func main() {
          many := []string{"12", "3", "b"}
          varFunc(many...)
          sum := oneByOne("Adding numbers...", 1, 2, 3, 4, 5, -1, 10)
          fmt.Println("Sum:", sum)
          sliceOfNumbers := []int{1, 2, 3}
          sum = oneByOne("Adding numbers...", sliceOfNumbers...)
          fmt.Println(sliceOfNumbers)
      }
      • The ... operator used as a prefix to a type like ...int is called the pack operator, whereas the unpack operator appends a slice like sliceOfNumbers.... A slice is a Go data type that is essentially an abstraction of an array of unspecified length.

      • Each variadic function can use the pack operator once. The oneByOne() function accepts a single string and a variable number of integer arguments using the sliceOfNumbers slice.

      • The varFunc function accepts a single argument and just calls the fmt.Println() function.

      • Another note about slices: the second call to oneByOne() is using a slice. Any changes you make to that slice inside the variadic function will persist after the function exits because this is how slices work in Go.

      1. Execute the variadic.go program:

        go run variadic.go
        

        The output will resemble the following

          
        [12 3 b]
        Adding numbers...
        0 1     1 2     2 3     3 4     4 5     5 -1     6 10
        Sum: 24
        Adding numbers...
        0 1     1 2     2 3
        [-1000 2 3]
            
        

      Functions and pointer variables

      Go supports pointers and this section will briefly present how functions can work with pointers. A future Go guide will talk about pointers in more detail, but here is a brief overview.

      ./fPointers.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      
      package main
      
      import (
          "fmt"
      )
      
      func getPointer(varToPointer *float64) float64 {
          return *varToPointer * *varToPointer
      }
      
      func returnPointer(testValue int) *int {
          squareTheTestValue := testValue * testValue
          return &squareTheTestValue
      }
      
      func main() {
          testValue := -12.12
          fmt.Println(getPointer(&testValue))
          testValue = -12
          fmt.Println(getPointer(&testValue))
      
          theSquare := returnPointer(10)
          fmt.Println("sq value:", *theSquare)
          fmt.Println("sq memory address:", theSquare)
      }
      • The getPointer() function takes a pointer argument to a float64, which is defined as varToPointer *float64, where returnPointer() returns a pointer to an int, which is declared as *int.
      1. Execute the fPointers.go program:

        go run fPointers.go
        

        The output will resemble the following:

          
        146.8944
        144
        sq value: 100
        sq memory address: 0xc00001a0b8
            
        

      Functions with Functions as Parameters

      Go functions can have functions as parameters.

      ./fArgF.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
      package main
      
      import "fmt"
      
      func doubleIt(numToDouble int) int {
          return numToDouble + numToDouble
      }
      
      func squareIt(numToSquare int) int {
          return numToSquare * numToSquare
      }
      
      func funFun(functionName func(int) int, variableName int) int {
          return functionName(variableName)
      }
      
      func main() {
          fmt.Println("funFun Double:", funFun(doubleIt, 12))
          fmt.Println("funFun Square:", funFun(squareIt, 12))
          fmt.Println("Inline", funFun(func(numToCube int) int { return numToCube * numToCube * numToCube }, 12))
      }
      • The funFun() function accepts two parameters, a function parameter named functionName and an int value. The functionName parameter should be a function that takes one int argument and returns an int value.

      • The first fmt.Println() call in main() uses funFun() and passes the doubleIt function, without any parentheses, as its first parameter.

      • The second fmt.Println() call uses funFun() with squareIt as its first parameter.

      • In the last fmt.Println() statement the implementation of the function parameter is defined inside the call to funFun() using an anonymous function.

      1. Execute the fArgF.go program:

        go run fArgF.go
        

        The output will resemble the following:

          
        function1: 24
        function2: 144
        Inline 1728
            
        

      Functions Returning Functions

      Go functions can return functions.

      ./fRetF.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      package main
      
      import (
          "fmt"
      )
      
      func squareFunction() func() int {
          numToSquare := 0
          return func() int {
              numToSquare++
              return numToSquare * numToSquare
          }
      }
      
      func main() {
          square1 := squareFunction()
          square2 := squareFunction()
      
          fmt.Println("First Call to square1:", square1())
          fmt.Println("Second Call to square1:", square1())
          fmt.Println("First Call to square2:", square2())
          fmt.Println("Third Call to square1:", square1())
      }
      • squareFunction() returns an anonymous function with the func() int signature.

      • As squareFunction() is called two times, you will need to use two separate variables, square1 and square2 to keep the two return values.

      1. Execute the fRetF.go program:

        go run fRetF.go
        

        Your output will resemble the following:

          
        First Call to square1: 1
        Second Call to square1: 4
        First Call to square2: 1
        Third Call to square1: 9
            
        

        Notice that the values of square1 and square2 are not connected even though they both came from squareFunction().

      Errors in Go

      Errors and error handling are two important topics in Go. Go puts so much importance on error messages that it has a dedicated data type for errors, aptly named error. This also means that you can easily create your own error messages if you find that what Go gives you is not adequate. You will most likely need to create and handle your own errors when you are developing your own Go packages.

      Recognizing an error condition is one task, while deciding how to react to an error condition is another task. Therefore, some error conditions might require that you immediately stop the execution of the program, whereas in other error situations, you might just print a warning message and continue.

      ./errors.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      
      package main
      
      import (
          "errors"
          "fmt"
          "strconv"
      )
      
      func main() {
      
          customError := errors.New("My Custom Error!")
          if customError.Error() == "My Custom Error!" {
              fmt.Println("!!")
          }
      
          stringToConvert1 := "123"
          stringToConvert2 := "43W"
          _, err := strconv.Atoi(stringToConvert1)
          if err != nil {
              fmt.Println(err)
              return
          }
      
          _, err = strconv.Atoi(stringToConvert2)
          if err != nil {
              fmt.Println(err)
              return
          }
      }
      • The strconv.Atoi() function tries to convert a string into an integer, provided that the string is a valid integer, and returns two things, an integer value and an error variable. If the error variable is nil, then the conversion was successful and you get a valid integer. The _ character tells Go to ignore one, as in this case, or more of the return values of a function.

      • Most of the time, you need to check whether an error variable is equal to nil and then act accordingly. This kind of Go code is very popular in Go programs and you will see it and use it multiple times.

      • Also presented here is the errors.New() function that allows you to create a custom error message and errors.Error() function that allows you to convert an error variable into a string variable.

      1. Execute the errors.go program:

        go run errors.go
        

        Your output will resemble the following:

          
        !!
        strconv.Atoi: parsing "43W": invalid syntax
            
        

      Summary

      In this guide you learned the basics about the Go programming language, how to execute programs, how to write loops, how to handle errors, and you saw examples for various function types.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link