Deploying a Static Site on Linode Kubernetes Engine

Updated by Linode Contributed by Linode

Contribute on GitHub

Report an Issue | View File | Edit File

Linode Kubernetes Engine (LKE) allows you to easily create, scale, and manage Kubernetes clusters to meet your application’s demands, reducing the often complicated cluster set-up process to just a few clicks. Linode manages your Kubernetes master node, and you select how many Linodes you want to add as worker nodes to your cluster.

Deploying a static site using an LKE cluster is a great example to follow when learning Kubernetes. A container image for a static site can be written in less than ten lines, and only one container image is needed, so it’s less complicated to deploy a static site on Kubernetes than some other applications that require multiple components.

Caution
Following the instructions in this guide will create billable resources on your account in the form of Linodes and NodeBalancers. You will be billed an hourly rate for the time that these resources exist on your account. Be sure to follow the tear-down section at the end of this guide if you do not wish to continue using these resources.

In this Guide

This guide will show you how to:

Before You Begin

Install kubectl

You should have kubectl installed on your local workstation. kubectl is the command line interface for Kubernetes, and allows you to remotely connect to your Kubernetes cluster to perform tasks.

macOS:

Install via Homebrew:

brew install kubernetes-cli

If you don’t have Homebrew installed, visit the Homebrew home page for instructions. Alternatively, you can manually install the binary; visit the Kubernetes documentation for instructions.

Linux:

  1. Download the latest kubectl release:

    curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
    
  2. Make the downloaded file executable:

    chmod +x ./kubectl
    
  3. Move the command into your PATH:

    sudo mv ./kubectl /usr/local/bin/kubectl
    
Note
You can also install kubectl via your package manager; visit the Kubernetes documentation for instructions.

Windows:

Visit the Kubernetes documentation for a link to the most recent Windows release.

Install Git

To perform some of the commands in this guide you will need to have Git installed on your workstation. Git is a version control system that allows you to save your codebase in various states to ease development and deployment. Follow our How to Install Git on Linux, Mac or Windows guide for instructions on how to install Git.

Install Docker

These steps install Docker Community Edition (CE) using the official Ubuntu repositories. To install on another distribution, or to install on Mac or Windows, see the official installation page.

  1. Remove any older installations of Docker that may be on your system:

    sudo apt remove docker docker-engine docker.io
    
  2. Make sure you have the necessary packages to allow the use of Docker’s repository:

    sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg
    
  3. Add Docker’s GPG key:

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    
  4. Verify the fingerprint of the GPG key:

    sudo apt-key fingerprint 0EBFCD88
    

    You should see output similar to the following:

      
    pub   rsa4096 2017-02-22 [SCEA]
          9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
    uid           [ unknown] Docker Release (CE deb) 
    sub   rsa4096 2017-02-22 [S]
    
    
  5. Add the stable Docker repository:

    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
    
    Note

    For Ubuntu 19.04, if you get an E: Package 'docker-ce' has no installation candidate error, this is because the stable version of docker is not yet available. Therefore, you will need to use the edge / test repository.

    sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable edge test"
    
  6. Update your package index and install Docker CE:

    sudo apt update
    sudo apt install docker-ce
    
  7. Add your limited Linux user account to the docker group:

    sudo usermod -aG docker $USER
    
    Note
    After entering the usermod command, you will need to close your SSH session and open a new one for this change to take effect.
  8. Check that the installation was successful by running the built-in “Hello World” program:

    docker run hello-world
    

Sign up for a Docker Hub Account

You will use Docker Hub to store your Docker image. If you don’t already have a Docker Hub account, create one now.

Install Hugo

A static site generator (SSG) is usually a command line tool that takes text files written in a markup language like Markdown, applies a stylized template to the content, and produces valid HTML, CSS, and JavaScript files. Static sites are prized for their simplicity and speed, as they do not generally have to interact with a database.

The Linode documentation website, and this guide, employ Hugo. Hugo is a powerful and fast SSG written in the Go programming language, but you can choose one that best suits your needs by reading our How to Choose a Static Site Generator guide.

The steps in this guide are generally the same across SSGs: install a static site generator, create some content in a text file, and then generate your site’s HTML through a build process.

To download and install Hugo, you can use a package manager.

  • For Debian and Ubuntu:

    sudo apt-get install hugo
    
  • For Red Hat, Fedora, and CentOS:

    sudo dnf install hugo
    
  • For macOS, use Homebrew:

    brew install hugo
    
  • For Windows, use Chocolatey:

    choco install hugo
    

For more information on downloading Hugo, you can visit the official Hugo website.

Create a Static Site Using Hugo

In this section you will create a static site on your workstation using Hugo.

  1. Use Hugo to scaffold a new site. This command will create a new directory with the name you provide, and inside that directory it will create the default Hugo directory structure and configuration files:

    hugo new site lke-example
    
  2. Move into the new directory:

    cd lke-example
    
  3. Initialize the directory as a Git repository. This will allow you to track changes to your website and save it in version control.

    git init
    
  4. Hugo allows for custom themes. For the sake of this example, you will install the Ananke theme as a Git submodule.

    git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke
    
    Note
    Git submodules allow you to include one Git repository within another, each maintaining their own version history. To view a collection of Hugo themes, visit the Hugo theme collection.
  5. In the text editor of your choice, open the config.toml file and add the following line to the end:

    theme = "ananke"
    

    This line instructs Hugo to search for a folder named ananke in the themes directory and applies the templating it finds to the static site.

  6. Add an example first post to your Hugo site:

    hugo new posts/first_post.md
    

    This will create a Markdown file in the content/posts/ directory with the name first_post.md. You will see output like the following:

      
    /Users/linode/k8s/lke/lke-example/content/posts/first_post.md created
    
    
  7. Open the first_post.md file in the text editor of your choosing. You will see a few lines of front matter, a format Hugo uses for extensible metadata, at the top of the file:

    lke-example/content/posts/first_post.md
    1
    2
    3
    4
    5
    
    ---
    title: "First_post"
    date: 2019-07-29T14:22:04-04:00
    draft: false
    ---

    Change the title to your desired value, and change draft to false. Then, add some example Markdown text to the bottom of the file, like the example below:

    lke-example/content/posts/first_post.md
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    ---
    title: "First Post About LKE Clusters"
    date: 2019-07-29T14:22:04-04:00
    draft: false
    ---
    
    ## LKE Clusters
    
    Linode Kubernetes Engine (LKE) clusters are:
    
    - Fast
    - Affordable
    - Scalable
  8. You can preview your changes by starting the local Hugo server:

    hugo server
    

    You should see output like the following:

      
                       | EN
    +------------------+----+
      Pages              |  8
      Paginator pages    |  0
      Non-page files     |  0
      Static files       |  3
      Processed images   |  0
      Aliases            |  0
      Sitemaps           |  1
      Cleaned            |  0
    
    Total in 6 ms
    Watching for changes in /Users/linode/k8s/lke/lke-example/{content,data,layouts,static,themes}
    Watching for config changes in /Users/linode/k8s/lke/lke-example/config.toml
    Serving pages from memory
    Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
    Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
    Press Ctrl+C to stop
    
    
  9. Visit the URL that Hugo is running on. In the above example, the URL is http://localhost:1313. This server automatically updates whenever you make a change to a file in the Hugo site directory. To stop this server, enter CTRL-C on your keyboard in your terminal.

  10. When you are satisfied with your static site, you can generate the HTML, CSS, and JavaScript for your site by building the site:

    hugo -v
    

    Hugo creates the site’s files in the public/ directory. View the files by listing them:

    ls public
    
  11. You can build the site at any time from your source Markdown content files, so it’s common practice to keep built files out of a Git repository. This practice keeps the size of the repository to a minimum.

    You can instruct Git to ignore certain files within a repository by adding them to a .gitignore file. Add the public/ directory to your .gitignore file to exclude these files from the repository:

    echo 'public/' >> .gitignore
    
  12. Add and commit the source files to the Git repository:

    git add .
    git commit -m "Initial commit. Includes all of the source files, configuration, and first post."
    

    You are now ready to create a Docker image from the static site you’ve just created.

Create a Docker Image

In this section you will create a Docker container for your static site, which you will then run on your LKE cluster. Before deploying it on your cluster, you’ll test its functionality on your workstation.

  1. In your Hugo static site folder, create a new text file named Dockerfile and open it in the text editor of your choosing. A Dockerfile tells Docker how to create the container.

  2. Add the following contents to the Dockerfile. Each command has accompanying comments that describe their function:

    lke-example/Dockerfile
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    # Install the latest Debain operating system.
    FROM debian:latest as HUGO
    
    # Install Hugo.
    RUN apt-get update -y
    RUN apt-get install hugo -y
    
    # Copy the contents of the current working directory to the
    # static-site directory.
    COPY . /static-site
    
    # Command Hugo to build the static site from the source files,
    # setting the destination to the public directory.
    RUN hugo -v --source=/static-site --destination=/static-site/public
    
    # Install NGINX, remove the default NGINX index.html file, and
    # copy the built static site files to the NGINX html directory.
    FROM nginx:stable-alpine
    RUN mv /usr/share/nginx/html/index.html /usr/share/nginx/html/old-index.html
    COPY --from=HUGO /static-site/public/ /usr/share/nginx/html/
    
    # Instruct the container to listen for requests on port 80 (HTTP).
    EXPOSE 80

    Save the Dockerfile and return to the command prompt.

  3. Create a new text file named .dockerignore in your Hugo static site folder and add the following lines:

    lke-example/.dockerignore
    1
    2
    3
    4
    
    public/
    .git/
    .gitmodules/
    .gitignore
    Note
    This file, similar to the .gitignore file you created in the previous section, allows you to ignore certain files within the working directory that you would like to leave out of the container. Because you want the container to be the smallest size possible, the .dockerignore file will include the public/ folder and some hidden folders that Git creates.
  4. Run the Docker build command. Replace mydockerhubusername with your Docker Hub username. The period at the end of the command tells Docker to use the current directory as its build context.

    docker build -t mydockerhubusername/lke-example:v1 .
    
    Note
    In the example below, the container image is named lke-example and has been given a version tag of v1. Feel free to change these values.
  5. Docker will download the required Debian and NGINX images, as well as install Hugo into the image. Once complete, you should see output similar to the following:

      
    Successfully built 320ae416c940
    Successfully tagged mydockerhubusername/lke-example:v1
    
    
  6. You can view the image by listing all local images:

    docker images
    
      
    REPOSITORY                       TAG   IMAGE ID       CREATED             SIZE
    mydockerhubusername/lke-example  v1    320ae416c940   About an hour ago   20.8MB
    
    

Test the Docker Image

  1. You can test your new image by creating a container with it locally. To do so, enter the following run command:

    docker run -p 8080:80 -d mydockerhubusername/lke-example:v1
    

    The -p flag instructs Docker to forward port 8080 on localhost to port 80 on the container. The -d flag instructs Docker to run in detached mode so that you are returned to the command prompt once the container initializes.

  2. Once the container has started, open your browser and navigate to localhost:8080. You should see your static site.

  3. You can stop the running container by finding the ID of the container and issuing the stop command. To find the ID of the container, use the ps command:

    docker ps
    

    You should see a list of actively running containers, similar to the following:

      
    b4a7b959a6c7        mydockerhubusername/lke-example:v1         "nginx -g 'daemon of…"   5 hours ago         Up 5 hours          0.0.0.0:8080->80/tcp        romantic_mahavira
    
    
  4. Note the random string of numbers and letters next to the image name. In the above example, the string is b4a7b959a6c7. Issue the stop command, supplying the string of numbers and letters:

    docker stop b4a7b959a6c7
    

Upload the Image to Docker Hub

  1. Now that you have a working container image, you can push that image to Docker Hub. First, log in to Docker Hub from your workstation’s terminal:

    docker login
    
  2. Next, push the image, with version tag, to Docker Hub, using the push command:

    docker push mydockerhubusername/lke-example:v1
    

    You can now view your image on Docker Hub as a repository. To view all of your repositories, navigate to the Docker Hub repository listing page.

  3. Lastly, add the Dockerfile and .dockerignore file to your Git repository:

    git add .
    git commit -m "Add Dockerfile and .dockerignore."
    

    You are now ready to deploy the container to your LKE cluster.

Deploying the Container to LKE

In this section, you will create a Deployment from the container you created in the previous section, and a Service to load balance the deployment.

  1. Begin by navigating to a location outside of your static site directory. You will not need your static site directory for the remainder of this guide.

    cd ..
    
  2. Create a new directory to house your Kubernetes manifests, and move into that directory:

    mkdir manifests && cd manifests
    

Create a Deployment

  1. In the text editor of your choice, create a new YAML manifest file for your Deployment. Name the file static-site-deployment.yaml, save it to your manifests directory, and enter the contents of this snippet:

    manifests/static-site-deployment.yaml
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: static-site-deployment
      labels:
        app: static-site
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: static-site
      template:
        metadata:
          labels:
            app: static-site
        spec:
          containers:
          - name: static-site
            image: mydockerhubusername/lke-example:v1
            imagePullPolicy: Always
            ports:
            - containerPort: 80
    • In this example the number of replica Pods is set to 3 on line 8. This value can be changed to meet the needs of your website.
    • The spec.containers.image field on line 19 should be changed to match the name of the container image you pushed to Docker Hub. Be sure to include the proper version tag at the end of the container name.
    • imagePullPolicy: Always on line 20 ensures that each time a Pod is created, the most recent version of the container image will be pulled from Docker Hub.
  2. Once you have a Deployment manifest, you can apply the deployment to the LKE cluster with kubectl:

    kubectl apply -f static-site-deployment.yaml
    
  3. You can check on the progress of your Deployment by listing the available pods:

    kubectl get pods
    

    If your Deployment was successful, you should see output like the following:

      
    NAME                                    READY   STATUS   RESTARTS   AGE
    static-site-deployment-cdb88b5bb-7pbjc  1/1     Running  0          1h
    static-site-deployment-cdb88b5bb-gx9h5  1/1     Running  0          1h
    static-site-deployment-cdb88b5bb-lzdvh  1/1     Running  0          1h
    
    

Create a Service

  1. Create a Service manifest file to provide load balancing for the deployment. Load balancing ensures that traffic is balanced efficiently across multiple backend nodes, improving site performance and ensuring that your static site will be accessible should a node go down.

    Specifically, the Service manifest that will be used in this guide will trigger the creation of a Linode NodeBalancer.

    Note
    The NodeBalancer’s creation is controlled through the Linode Cloud Controller Manager (CCM). The CCM provides a number of settings, called annotations, that allow you to control the functionality of the NodeBalancer. To learn more about the CCM, read our Deploying NodeBalancers with the Linode CCM guide.
  2. Name the file static-site-service.yaml, save it to your manifests directory, and enter the contents of this snippet:

    manifests/static-site-service.yaml
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    apiVersion: v1
    kind: Service
    metadata:
      name: static-site-service
      annotations:
        service.beta.kubernetes.io/linode-loadbalancer-throttle: "4"
      labels:
        app: static-site
    spec:
      type: LoadBalancer
      ports:
      - name: http
        port: 80
        protocol: TCP
        targetPort: 80
      selector:
        app: static-site
      sessionAffinity: None
  3. Once you’ve created your Service manifest file, you can apply it to the LKE cluster:

    kubectl apply -f static-site-service.yaml
    
  4. You can check on the status of your Service by listing the Services currently running on your server:

    kubectl get services
    

    You should see output similar to the following:

      
    NAME                 TYPE          CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
    kubernetes           ClusterIP     10.128.0.1     <none>           443/TCP        20h
    static-site-service  LoadBalancer  10.128.99.240  192.0.2.1        80:32648/TCP   100m
    
    
  5. Note the external IP address of the Service you created. This is the IP address of the NodeBalancer, and you can use it to view your static site.

  6. In the above example, the IP address is 192.0.2.1. Navigate to the external IP address in the browser of your choice to view your static site. You should see the same content as when you tested your Docker image on your workstation.

General Network and Firewall Information

In an LKE cluster, both of the following types of workload endpoints cannot be reached from the Internet:

  • Pod IPs, which use a per-cluster virtual network in the range 10.2.0.0/16

  • ClusterIP Services, which use a per-cluster virtual network in the range 10.128.0.0/16

All of the following types of workloads can be reached from the Internet:

  • NodePort Services, which listen on all Nodes with ports in the range 30000-32768.

  • LoadBalancer Services, which automatically deploy and configure a NodeBalancer.

  • Any manifest which uses hostNetwork: true and specifies a port.

  • Most manifests which use hostPort and specify a port.

Exposing workloads to the public Internet through the above methods can be convenient, but they can also carry a security risk. You may wish to manually install firewall rules on your cluster nodes; to do so, please see this community post. Linode is developing services which will allow for greater flexibility for the network endpoints of these types of workloads in the future.

Next Steps

If you’d like to continue using the static site that you created in this guide, you may want to assign a domain to it. Review the DNS Records: An Introduction and DNS Manager guides for help with setting up DNS. When setting up your DNS record, use the external IP address that you noted at the end of the previous section.

If you would rather not continue using the cluster you just created, review the tear-down section to remove the billable Linode resources that were generated.

Tear Down your LKE Cluster and NodeBalancer

  • To remove the NodeBalancer you created, all you need to do is delete the underlying Service. From your workstation:

    kubectl delete service static-site-service
    

    Alternatively, you can use the manifest file you created to delete the Service. From your workstation:

    kubectl delete -f static-site-service.yaml
    
  • To remove the LKE Cluster and the associated nodes from your account, navigate to the Linode Cloud Manager:

    1. Click on the Kubernetes link in the sidebar. A new page with a table which lists your clusters will appear.

    2. Click on the more options elipsis next to the cluster you would like to delete, and select Delete.

    3. You will be prompted to enter the name of the cluster to confirm the action. Enter the cluster name and click Delete.

  • Lastly, remove the KUBECONFIG line you added to your Bash profile to remove the LKE cluster from your available contexts.

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.

Join our Community

Find answers, ask questions, and help others.

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