[ Language select: 日本語 ]

1. Creating search page by OpenAPI+Sinatra

Using Docker, let https://opm00h.u-aizu.ac.jp/solr/api/v1/search handle the search process and create an application to create and display a web page that displays the search results. Create an application to create and display a page that displays the search results.

1.1. About OpenAPI and openapi-generator

OpenAPI is the name of the specification created by the OpenAPI Initiative, an organization that aims to standardize methods for writing APIs.

openapi-generator is a tool that generates a corresponding programming code template based on the design document conforming to OpenAPI, which is currently being created by volunteers due to various circumstances.

1.2. Preparation work

Please see the following document and prepare the file, ~/.config/containers/storage.conf, to use the podman command.

We have registered the package on github.com to start the work. Create a working directory (docker-sccp-sinatra-sample) at an arbitrary location with the following command.

$ git clone https://github.com/YasuhiroABE/docker-sccp-sinatra-sample.git
$ cd docker-sccp-sinatra-sample

If you would like to use your own PC, please install the openapi-generator-cli command based on the official guide.

If your command name is different, such as "openapi-generator", please modify the command name of your Makefile by hand.

If you use the macOS, the bundled ruby command seems to be too old. Please update the ruby command by homebrew as follows.

$ brew install ruby
$ export PATH=/opt/homebrew/opt/ruby/bin:${PATH}

1.3. Updating to the latest code

If the contents of GitHub are updated during the course of the exercise, or if you want to bring the contents of the directory created by git clone up to date, execute the following command.

## Execute the following command at the "docker-sccp-sinatra-sample" directory.
$ git pull

1.4. Let’s run it first.

It won’t work in any meaningful way, but you can check what it does anyway. First, run the command as follows: make sure you are in the docker-sccp-sinatra-sample/ directory before proceeding.

$ make gen-code
$ cd code
$ make PORT=8080 run
...
bundle exec rackup --host 0.0.0.0 --port 8080
Puma starting in single mode...
* Puma version: 6.4.2 (ruby 3.2.3-p157) ("The Eagle of Durango")
*  Min threads: 0
*  Max threads: 5
*  Environment: development
*          PID: 5187
* Listening on http://0.0.0.0:8080
Use Ctrl-C to stop

If you just enter the command as "make run", the port number will be a random number between 5000 and 9999. Please use it to avoid port number conflicts when sharing with multiple people.

Next, start a web browser, display the URL, http://localhost:8080/search , and verify that one line of the result is displayed.

You can see the following message in the browser.

{"message": "yes, it worked"}

At this point, stop the execution of the make run command by pressing Ctrl-c or something similar.

1.4.1. Example of command execution up to this point

1.4.2. Errors when executing make gen-code

Errors may occur when executing make gen-code, such as the version of the openapi-generator-cli command does not match the installed one.

Download 5.3.0 ...
/usr/local/lib/node_modules/@openapitools/openapi-generator-cli/node_modules/fs-extra/lib/move/move-sync.js:40
    if (err.code !== 'EXDEV') throw err

Error: EACCES: permission denied, rename '/tmp/generator-cli-Le849S/5.3.0' -> '/usr/local/lib/node_modules/@op
enapitools/openapi-generator-cli/versions/5.3.0.jar'
...

In this case, delete the openapitools.json file in the current directory (CWD).

## When the error happened, execute the following command and re-run the "make gen-code" again
$ rm openapitools.json

1.5. Instructions that make command is executing.

Makefile describes the actual process (a list of commands) corresponding to make gen-code or make run.

The code corresponding to the make gen-code and make run in the Makefile is as follows.

OAGEN_CLI = openapi-generator-cli

gen-code:
        $(OAGEN_CLI) generate -g ruby-sinatra -o code -i openapi.yaml
        cp _docker/Makefile code/
        cp _docker/Dockerfile code/
        cp _docker/run.sh code/
        cp _docker/Gemfile code/
        cp _docker/config.ru code/
        mkdir -p code/lib/views
        cp _docker/header.erb code/lib/views/
        cp _docker/main.erb code/lib/views/
        cp _docker/footer.erb code/lib/views/

The code corresponding to run in code/Makefile (_docker/Makefile) is a bit simpler, but somewhat more complex.

run: bundle-install
        bundle exec rackup --host $(HOST) --port $(PORT)

bundle-install:
        bundle config set path lib
        bundle install

If you run make run, the same process as if you had run make bundle-install will be performed before processing the body.

This allows you to separate the preparation of necessary library files, etc. and the execution of the program, while writing them as a series of processes with a flow.

By modifying the contents of the Makefile based on this sample, it is possible to output code corresponding to a processing system other than the Ruby language + Sinatra framework.

1.6. Change the output result

First, look for a place that displays {"message": "yes, it worked"} when accessing /search.

Open api/default_api.rb in emacs, etc. and look for the location where the "yes, it worked" message is displayed.

There are two places where it says "yes, it worked", choose the one that corresponds to /search.

Once you have chosen the line to edit, delete the line {"message": "yes, it worked"} and insert the following code. See also the line # the guts live here.

  • Open the "api/default_api.rb" file, then edit the "/search" section as follows.

  # the guts live here
  ## {"message" => "yes, it worked"}.to_json

  require 'uri'
  require 'httpclient'
  client = HTTPClient.new
  url = URI::HTTPS.build({:host => "opm00h.u-aizu.ac.jp", :path => '/solr/api/v1/search', :query => "q=sccp&wt=json&site="})
  ret = client.get(url)
  @result = JSON.parse(ret.body)
  @result.to_json
end

Be careful not to duplicate end.

After saving the modified file, run make PORT=8080 run again.

If the changes were made successfully, you will see the following message as before. If there is a mistake, the Listening on . message will not be displayed, but an error message will be displayed, so compare and correct.

$ make run
...
bundle exec rackup --host 0.0.0.0 --port 8080
Puma starting in single mode...
* Puma version: 5.2.2 (ruby 2.7.0-p0) ("Fettisdagsbulle")
*  Min threads: 0
*  Max threads: 5
*  Environment: development
*          PID: 103170
* Listening on http://0.0.0.0:8080

Also open http://localhost:8080/search in a web browser and check the results.

You can also access the API and check the results from a terminal with the following command. This one will be formatted with the jq command, so it should be easier to see.

## 別の端末を開いて次のコマンドを実行します。
$ curl http://localhost:8080/search | jq .

The output of the curl command has the following nested structure

{
  "responseHeader": {
    "zkConnected": true,
    ...
  },
  "response": {
    "numFound": 3344,
    "start": 0,
    "maxScore": 489.15277,
    "docs": [
      {
        "id": "https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/lda4bcl/",
        "title": [
        ...
        ]
      }
    ]
  },
 "highlighting": {
    "https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/lda4bcl/": {
      "content": [
      ...
      ]
    }
  }
}

Once this is done, stop execution of the make run command by typing Ctrl-c, for example.

1.6.1. Example of work to change output results

1.7. Further modifying the output result

Now we have a fixed search result for the word sccp. We will make it possible to search for arbitrary keywords later, but first we will change the output.

We will add more processing at the end of the place we just added. The first @result.to_json and the last end are written in api/default_api.rb. Add a line output = erb :main between them.

 @result.to_json

 output = erb :main
end

This time, only one line was added, starting with output.

When you are done, save the file and run the make run command again.

$ make run

Again access http://localhost:8080/search in your web browser.

You should see a list of search results as follows

tutorial websample sinatra.search results overview

Here the display on the screen is controlled by the lib/views/main.erb file.

1.8. Make any string searchable.

Currently, only the keyword sccp is searched for and cannot be changed. The following section of the api/default_api.rb file should be changed.

url = URI::HTTPS.build({:host => "opm00h.u-aizu.ac.jp", :path =>'/solr/api/v1/search', :query => "q=sccp&wt=json&site="})

You can change the search results by specifying the keywords you want to search for in the part following q=.

Delete the line above that begins with url = and replace it with the following two lines

@param_q = params.has_key?(:q) ? params[:q].to_str  : "sccp"
url = URI::HTTPS.build({:host => "opm00h.u-aizu.ac.jp", :path => '/solr/api/v1/search', :query => "q=#{@param_q}&wt=json&site="})
Results including before and after changes
  # the guts live here

  require 'uri'
  require 'httpclient'
  client = HTTPClient.new
  @param_q = params.has_key?(:q) ? params[:q].to_str  : "sccp"
  url = URI::HTTPS.build({:host => "opm00h.u-aizu.ac.jp", :path => '/solr/api/v1/search', :query => "q=#{@param_q}&wt=json&site="})
  ret = client.get(url)
  @result = JSON.parse(ret.body)
  @result.to_json

  output = erb :main
end

Again, start the web server with the make run command, and access the URL, http://localhost:8080/search, by your web browser.

Then, enter an appropriate string in the dialog box at the bottom of the screen and click the "Search" button. The search results will change by using the specified string as a keyword.

1.9. Display the search string on the screen

As it is now, it is difficult to understand what you are searching for. To improve this, the text field at the bottom of the screen will display the current search string in advance.

Open the file lib/views/main.erb with an editor, such as emacs. Move to the bottom of the file and change the contents of the input tags surrounded by <form …​>〜</form> like as follows.

<form method="get" action="" >
<input type="text" name="q" value="<%= Rack::Utils.escape_html(@param_q) %>" /><button type="submit">Search</button>
</form>

Save your edits and reload the screen displayed in your web browser to see the results.

【Note】
If you edit the *.erb file, there is no need to restart the application (C-c & make run).
It can be reflected by reloading (reloading) the screen.

tutorial websample sinatra.form value sccp

1.10. How ERB (Erubi) works

ERB is a Ruby-specific function, and this time we are using a highly functional compatible library called Erubi, which adds even more functionality.

By using ERB(Erubi), you can embed Ruby language program codes in various text files. In this case, the code is written directly in the HTML file.

The following code will automatically search for the corresponding file (main.erb) in the lib/views/ directory.

output = erb :main

Assigns the contents of the lib/views/main.erb file to the output variable, which executes the embedded program and reflects it in the output.

1.10.1. About lib/views/main.erb

The entire main.erb file looks like this

<h2>Search Results</h2>
<ul>
<% for item in @result["response"]["docs"] %>
  <li><a href="<%= item['id'] %>"><%= item['id'] %></a><br /><% unless @result['highlighting'][item['id']]["content"].to_s == "" %><%= @result['highlighting'][item['id']]["content"][0].to_json %><% end %></li>
<% end %>
</ul>
<form method="get" action="" >
<input type="text" name="q" value="<%= Rack::Utils.escape_html(@param_q) %>" /><button type="submit">Search</button>
</form>

The ERB file is a Ruby language proprietary format called ERB, which starts with <% and ends with % and ends with %.

The rest of the file is output as is, so here in the HTML file, The rest of the code is output as is, and is used here to embed the Ruby language code in the HTML file.

Here, a for statement is used to display the 10 search results from the @result variable. In addition, the input element embeds the contents of the @param_q variable in the value attribute.

1.11. Improving the appearance of the web page

In general, CSS and JavaScript are used to improve the appearance of Web pages. CSS/JavaScript is not used in the code up to this point.

The reason is that Sinatra does not have the ability to provide external files for CSS and JavaScript. While not technically impossible, it is common practice to prepare a separate web server to serve external files.

Therefore, if you wish to use CSS/JavaScript, the most practical way is to embed the CSS/JavaScript code in the HTML file.

The files used to create the look and feel of the web page within the application are located in the lib/views/ directory. You are free to rewrite any of the files as you wish.

1.11.1. [Preparation] Adding other files in lib/views/

Edit the api/default_api.rb file and add files other than main.erb. Change the part where output = erb :main as follows

output = erb :header
output += erb :main
output += erb :footer

Again, run the `make run command to access http://localhost:8080/search .

It is possible to combine all the contents into one main.erb file without splitting it into three files. However, in general, in order to make the appearance of each page consistent, code should be written in header.erb and footer.erb to create a common look and feel for each page.

In main.erb, the code necessary to output the screen for the application is written.

1.11.2. Example of changes made so far

1.11.3. [Work 1] Change the look of the screen using Bootstrap 5

For example, simply preparing to use Bootstrap5, a CSS framework, will change the appearance of the screen considerably.

Open the lib/views/header.erb file in your editor and copy the entire <head>~</head> between <html>~<body> so that it looks like this

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
        rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
        crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2"
        crossorigin="anonymous"></script>
    <style type="text/css">
        body {
            background-color: #ffffff;
        }
    </style>
</head>
<body>

If you check the text from a web browser after this, you will see that the font size and color have changed slightly.

1.11.4. [Work 2] Display the content without {}

In the search results {"contents":[…​.} in the search results. To change this, the following method is possible.

Modify lib/views/main.erb as follows

### Before modification
   <li><a href="<%= item['id'] %>"><%= item['id'] %></a><%= @result['highlighting'][item['id']].to_json %></li>

### After change
   <li><a href="<%= item['id'] %>"><%= item['id'] %></a><br /><%= @result['highlighting'][item['id']]["content"][0].to_json %></li>

In addition, we have changed the look a bit by putting a line break <br /> in between.

1.11.5. [Work 3] Tips for this and other appearance changes

While there are limits to how much you can change with no knowledge of the Ruby language, there are several ways to get clues for customizing.

In [Work 2], the item associative array (hash) variable and the @result variable are used, but they cannot be used without knowing the internal structure. Edit the lib/views/main.erbuis file as follows to understand the internals of each variable.

<%= item.inspect %>
<hr />
<%= @result["highlighting"].inspect %>
<hr />

1.12. [Answer key] Apply all the work done up to this point.

The api/default_api.rb file, equivalent to what you have edited so far, is located at

If you git clone the latest version, it is contained in the docker-sccp-sinatra-sample/ directory.

The following command will catch up with what we have done so far.

$ cp ../api.default_api.rb api/default_api.rb
$ cp ../views.header.erb lib/views/header.erb
$ cp ../views.main.erb lib/views/main.erb
$ make run

1.13. [Error response] Error message "Address already in use" appears.

The message appears when trying to open the port 8080 that is already in use by opening another terminal and executing make run in duplicate, etc., even though the ``$ make run command has already been executed.

$ make run
env FORM_BASEURI="http://127.0.0.1:8080/search" \
        bundle exec rackup --host 127.0.0.1 --port 8080
[2019-12-16 17:31:09] INFO  WEBrick 1.4.2
[2019-12-16 17:31:09] INFO  ruby 2.5.1 (2018-03-29) [x86_64-linux-gnu]
Traceback (most recent call last):
        16: from /usr/local/bin/rackup:23:in `<main>'
        15: from /usr/local/bin/rackup:23:in `load'
        14: from /var/lib/gems/2.5.0/gems/rack-2.0.7/bin/rackup:4:in `<top (required)>'
        13: from /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/server.rb:148:in `start'
        12: from /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/server.rb:297:in `start'
        11: from /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/handler/webrick.rb:31:in `run'
        10: from /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/handler/webrick.rb:31:in `new'
         9: from /usr/lib/ruby/2.5.0/webrick/httpserver.rb:47:in `initialize'
         8: from /usr/lib/ruby/2.5.0/webrick/server.rb:108:in `initialize'
         7: from /usr/lib/ruby/2.5.0/webrick/server.rb:127:in `listen'
         6: from /usr/lib/ruby/2.5.0/webrick/utils.rb:65:in `create_listeners'
         5: from /usr/lib/ruby/2.5.0/socket.rb:762:in `tcp_server_sockets'
         4: from /usr/lib/ruby/2.5.0/socket.rb:227:in `foreach'
         3: from /usr/lib/ruby/2.5.0/socket.rb:227:in `each'
         2: from /usr/lib/ruby/2.5.0/socket.rb:764:in `block in tcp_server_sockets'
         1: from /usr/lib/ruby/2.5.0/socket.rb:201:in `listen'
/usr/lib/ruby/2.5.0/socket.rb:201:in `bind': Address already in use - bind(2) for 127.0.0.1:8080 (Errno::EADDRINUSE)
Makefile:15: recipe for target 'run' failed

The cause may be that you or another user who has previously used the terminal has left a program running that uses port 8080.

In this case, stop the process that is already using port 8080 so that you can use port 8080.

## Use the lsof command to list the processes that are using the port number.
$ sudo lsof -i -P | grep 8080
ruby2.7   7355    s12xxxxx    5u  IPv4 146566      0t0  TCP *:8080 (LISTEN)

## Specify the second number from the left (process ID) to stop the process." The "7355" part changes every time.
$ sudo kill 7355

2. Summary

At this stage, the basic building blocks of a web application are in place, albeit with some visual appearance and missing functionality.

Still missing features and appearance can be improved.

  • Improve the look and feel of the page displaying the title and results throughout the screen.

  • Issue where only the first 10 results are displayed, even if there are more than 10 results

  • Improvement of the error that occurs when params['q'] is "" (empty string) because result['response']['docs'] does not exist (Fixed!)

For more information on how to improve this issue, please check Bootstrap5, or consider branching or limiting the display target in the code.

2.1. [For Advanced Students] Implement the pagination function

Pagination is used when the number of items to be displayed exceeds the range of one page.

We need the following information to get the search results to be displayed on the page from an arbitrary starting page number.

  1. Maximum number of items to be displayed on one page (e.g., 10 items/page)

  2. The starting number of the item to be displayed (e.g., 10th to 19th items to be displayed → 10)

If you want to show the current page number, or if you want not to move beyond the last page, you need to use more conditional branches.

For example, you can change the part from @param_q = .. to ret = client.get(url) as follows.

  @param_q = params.has_key?(:q) ? params[:q].to_str  : "sccp"
  @param_start = params.has_key?(:start) ? params[:start].to_i  : 0
  @param_rows = params.has_key?(:rows) ? params[:rows].to_i  : 10
  url = URI::HTTPS.build({:host => "opm00h.u-aizu.ac.jp", :path => '/solr/api/v1/search', :query => "q=#{@param_q}&wt=json&site=&start=#{@param_start}&rows=#{@param_rows}"})
  ret = client.get(url)

By the following URL, you can get the data from the 11th to the 30th items.

Then, we will add buttons such as "Back to the previous page" and "Next page" to the page.

First, place a file named lib/view/pagination.erb with the following code.

<%# -*- mode: web -*- %>
<% num_pages = (1.0 * @result["response"]["numFound"].to_i / @param_rows).round %>
<% num_max_items = (num_pages < @param_rows) ? num_pages : @param_rows %>
<% start_index = ( @param_start.to_i / 10 ) * 10  %>
<% end_index = ( @result["response"]["numFound"].to_i / 10 ) * 10 %>

<% prev_start = ( @param_start - @param_rows > 0 ) ? @param_start - @param_rows : 0 %>
<% next_start = ( @param_start + @param_rows < @result["response"]["numFound"].to_i ) ? @param_start + @param_rows : @result["response"]["numFound"].to_i %>

<% # show first button %>
<a style="margin-left:1em;" href="?q=<%= Rack::Utils.escape_html(@param_q) %>&start=0&rows=<%= @param_rows %>"><button class="btn btn-outline-primary btn-sm">First</button></a>

<a style="" href="?q=<%= Rack::Utils.escape_html(@param_q) %>&start=<%= prev_start %>&rows=<%= @param_rows %>"><button class="btn btn-outline-primary btn-sm">Previous</button></a>

<button type="button" class="btn btn-default ms-3"
disabled="disabled"><%= @param_start %> / <%=
@result["response"]["numFound"].to_i %></button>

<a style="margin-left:1em;" href="?q=<%= Rack::Utils.escape_html(@param_q) %>&start=<%= next_start %>&rows=<%= @param_rows %>"><button class="btn btn-outline-primary btn-sm">Next</button></a>

<% # show last button %>
<a href="?q=<%= Rack::Utils.escape_html(@param_q) %>&start=<%= end_index %>&rows=<%= @param_rows %>"><button class="btn btn-outline-primary btn-sm">Last</button></a>

Then, the file lib/views/main.erb calls this file as follows.

...
<h2>Search Results</h2>
<%= erb :pagination %>
<ul>
...

You can also add the same code at the bottom of the application.

3. Running the application with Docker

If you use the ThinkPad of the seminar room, please enter the following commands to prepare the podman command.

If you use your own laptop pc or something, please prepare the Docker Desktop by yourself.

3.1. Preparation

3.1.1. Using the podman command

Edit the Makefile file and change the following line.

DOCKER_CMD = "podman"

3.1.2. Setting up the Harbor

Please make sure that you have already registered your "Project" named as same as your namespace on the Harbor.

3.2. Build your container image and run it

The Makefile describes the work required to run the application as a Docker container.

$ make docker-build
$ make docker-build-prod
$ make docker-tag

## push image to Harbor
$ podman login inovtst9.u-aizu.ac.jp
$ make docker-push
$ podman logout inovtst9.u-aizu.ac.jp

## run container for test
$ make PORT=8080 docker-run

3.2.1. When using the macOS

You can build both aarm64 and amd64 images using the following method.

$ make docker-buildx-init    ## Run at one time only. Please ignore the error message.
$ make docker-buildx-setup

## build an image and push the image to Harbor
$ docker login inovtst9.u-aizu.ac.jp
$ make docker-buildx-prod
$ docker logout inovtst9.u-aizu.ac.jp

## run container for test
$ make PORT=8080 docker-runx

These two commands will prepare and start the Docker container. Once successfully launched, you can run the application from a web browser.

You can stop it with C-c.

Look inside the Makefile to see the commands being executed, respectively.

The execution result should be the same as before.

However, by packaging it as a Docker container, it is expected to work on other computers without requiring applications or libraries other than the docker command, just like the development environment.

4. Using from Kubernetes

Let’s try to use the Docker image registered with Harbor.

As an example of using a web application, we will use Official Docker Tutorial Part2.

Here, please download the following file and proceed. [ Download: tutorial-websample-sinatra.01.deploy-sinatra.yaml ]

$ wget https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-websample-sinatra.01.deploy-sinatra.yaml

The contents of the downloaded 01.deploy-sinatra.yaml file should look like this

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sinatra-webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sinatra-webapp
  template:
    metadata:
      labels:
        app: sinatra-webapp
    spec:
      containers:
      - name: sinatra-webapp
        image: inovtst9.u-aizu.ac.jp/yasu-abe/sinatra-webapp:0.1.1
        imagePullPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: sinatra-webapp
  labels:
    app: sinatra-webapp
spec:
  type: ClusterIP
  selector:
    app: sinatra-webapp
  ports:
  - port: 80
    targetPort: 8080

Change "yasu-abe" in the file to the Project name of the first Harbor you created (the same as your AINS ID) and then apply it to kubernetes as follows

$ kubectl -n $(id -un) apply -f tutorial-websample-sinatra.01.deploy-sinatra.yaml

If you see a message like service/sinatra-webapp configured, you have succeeded. Check that the configuration has been applied and that the Pod and Service have been successfully created.

$ kubectl -n $(id -un) get all -l app=sinatra-webapp

You should get output like this

NAME                                  READY   STATUS    RESTARTS   AGE
pod/sinatra-webapp-7ffc8dbcdd-hfvzp   1/1     Running   0          6m19s

NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/sinatra-webapp   ClusterIP   10.233.30.149   <none>        80/TCP    3m42s

NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/sinatra-webapp-7ffc8dbcdd   1         1         1       6m19s

If anything is missing, make sure you have edited the YAML file correctly.

4.1. Connecting to the application using Proxy

The method of using LoadBalancer from a web browser is limited to the total number of EXTERNAL-IPs, so use a reverse proxy prepared by each user.

Please check the following link for details on setting up a reverse proxy.

The proxy_pass line to be added at this time would look like this

      proxy_pass http://sinatra-webapp/;

Make sure you can access your application via inovtst9.u-aizu.ac.jp as follows

4.1.1. When you have an issue with your proxy server

If you have an problem of your proxy server, you can override your current proxy setting by as follows.

$ curl "https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-websample-sinatra.configmap-proxy.yaml" | sed -e "s/s12xxxxx/$(id -un)/" | kubectl -n $(id -un) apply -f -

$ kubectl -n $(id -un) apply -f https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/ingress-proxy.deploy-proxy.yaml

$ curl "https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/ingress-proxy.svc-proxy.yaml" | sed -e "s/s12xxxxx/$(id -un)/" | kubectl -n $(id -un) apply -f -

$ kubectl -n $(id -un) delete pod -l app=my-proxy

4.2. Troubleshoot if it doesn’t work well on Kubernetes

When things go wrong, the steps to be taken are: understand the current situation → analyze → take action.

To do this, you need to have some understanding of what you are trying to do. It may be difficult to get it right, and it may be hard, but please do your best.

4.2.1. Check the status

Run the previous command again and confirm that you see the three statuses: pod, service, and replicaset.apps.

## Same command as executed in above
$ kubectl -n $(id -un) get all -l app=sinatra-webapp

If any line is not shown, it means it was not created correctly, so create the missing definitions from kubectl apply again, using YAML files.

4.2.2. Check the status if the Pod is working

Check the status of the Pod only from the command you just executed.

$ kubectl -n $(id -un) get pod -l app=sinatra-webapp

You should see the following result on your screen

NAME                                  READY   STATUS    RESTARTS   AGE
pod/sinatra-webapp-7ffc8dbcdd-hfvzp   1/1     Running   0          6m19s

The 6m19s part of this last AGE shows how long the pod has been running. If you are redoing work, for example, check to see if you should have rebooted, but it has been running for the last week or so.

Also, check the Running part of STATUS to see if any other messages are displayed. Any status other than Running will not allow you to access the site from a web browser.

Applying this, check the output of the following command to see if the Proxy pod is working properly.

$ kubectl -n $(id -un) get pod -l app=my-proxy

If the Proxy seems to be running all the time, you can use reverse proxy - Use Proxy to connect to your service and restart by removing the Proxy pod.

4.2.3. Then check the Proxy settings

If restarting the proxy pod in ↑ has not improved the situation, check the contents of cm/nginx-conf.

$ kubectl -n $(id -un) get cm nginx-conf -o yaml

You should get output like this

apiVersion: v1
data:
  proxy.conf: |
    server {
      listen 80;
      location /yasu-abe/search/ {
         proxy_pass http://sinatra-webapp/;
      }
      location /yasu-abe/s/ {
         proxy_pass http://sinatra-webapp/;
      }
    }
kind: ConfigMap
metadata:

annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"proxy.conf":"server {\n  listen 80;\n  location /yasu-abe/ {\n    proxy_pass
    http://my-nginx/;\n  }\n}\n"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"app":"my-proxy"},
"name":"nginx-conf","namespace":"yasu-abe"}}
  creationTimestamp: "2022-04-25T11:30:53Z"
  labels:
    app: my-proxy
  name: nginx-conf
  namespace: yasu-abe
  resourceVersion: "97659475"
  uid: b9dd26f0-dd91-43b9-987d-58e818f30d99

The contents from the data: line to the kind: ConfigMap line are the contents of the nginx configuration.

Depending on what you have done so far, this content may vary. It does not have to be exactly the same.

If you are having trouble, change this content to a minimum.

  proxy.conf: |                                  ## ← don't change it Don't delete the "|"!
    server {                                     ## Do not change this line!
      listen 80; ## Also, do not change this line!
      location /yasu-abe/search/ {               ## ← Change "yasu-abe" to your ID
         proxy_pass http://sinatra-webapp/;      ## Do not change this line either.
      }                                          ## Do not change this line either, and do not forget the closing brackets corresponding to the server {
4.2.3.1. cm/nginx-conf trouble example

If you delete the leading "|" character in the above example, you will get the following screen output.

apiVersion: v1
data:
  proxy.conf: server { listen 80; location /yasu-abe/search/ { proxy_pass http://sinatra-webapp/;
    } location /yasu-abe/s/ { proxy_pass http://sinatra-webapp/; } }
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"proxy.conf":"server {\n  listen 80;\n  location /yasu-abe/ {\n    proxy_pass
    http://my-nginx/;\n  }\n}\n"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"app":"my-proxy"},
"name":"nginx-conf","namespace":"yasu-abe"}}
  creationTimestamp: "2022-04-25T11:30:53Z"
  labels:
    app: my-proxy
  name: nginx-conf
  namespace: yasu-abe
  resourceVersion: "99884213"
  uid: b9dd26f0-dd91-43b9-987d-58e818f30d99

The proxy works correctly in this state, although the data: lines are crushed and very difficult to see. However, it is very difficult for a human to add the correct settings, so it is recommended to modify proxy.conf: | to the original state.

To edit, use Reverse Proxy to find where the kubectl edit command is running and check the command.

Basically, these methods should take care of most problems.

4.2.4. Initializing Proxy Settings and Connecting to http://sinatra-webapp/

We will summarize the procedure as a last resort in case the Proxy does not work properly.

This is a last resort to override existing settings and connect directly from your own URL (https://inovtst9.u-aizu.ac.jp/s13xxxxx/) in the URL list.

Note that both procedures will overwrite existing objects and information will be lost.

4.2.4.1. initializing deploy/my-proxy

This step is generally not necessary, but is required if the reverse proxy pod (my-proxy) is not yet running.

Run the following commands in the Reverse Proxy just to be sure.

Running it more than once will not cause any problems.

$ kubectl -n $(id -un) apply -f https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/ingress-proxy.deploy-proxy.yaml
4.2.4.2. initializing cm/nginx-conf

Execute the following command with the above configuration file;

$ curl "https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/tutorial-websample-sinatra.configmap-proxy.yaml" | sed -e "s/s12xxxxx/$(id -un)/" | kubectl -n $(id -un) apply -f -
4.2.4.3. initialize svc/s13xxxxxxx-svc

The pods that match the label specified in selector: are accessed, but in many cases this is not app: my-proxy.

$ curl "https://web-int.u-aizu.ac.jp/~yasu-abe/ja/sccp/manual/ingress-proxy.svc-proxy.yaml" | sed -e "s/s12xxxxx/$(id -un)/" | kubectl -n $(id -un) apply -f -
4.2.4.4. Restart your Proxy Server
$ kubectl -n $(id -un) rollout restart deploy proxy
4.2.4.5. Confirmation of Operation

Go back to the URL list and click on your URL.