Materiały do zajęć

Chmura

Heroku

Inne platformy PaaS:

Zadania na zajęcia

Aplikacja w Javie na podstawie szablonu

  1. Zarejestruj się w usłudze Heroku:

    https://signup.heroku.com/
  2. Zainstaluj narzędzie do zarządzania aplikacjami w chmurze Heroku:

    $ cd /dev/shm && wget https://cli-assets.heroku.com/heroku-linux-x64.tar.gz && tar xvf heroku-linux-x64.tar.gz
  3. Zaloguj się w zainstalowanym narzędziu:

    $ /dev/shm/heroku/bin/heroku login
  4. Utwórz z repozytorium git-a przykładowy projekt startowy w Javie (używający frameworka Spring):

    $ cd ~
    $ git clone https://github.com/heroku/java-getting-started
    $ cd java-getting-started
  5. Utwórz nową aplikację na Heroku i wyświetl informacje o niej:

    $ appName=$(/dev/shm/heroku/bin/heroku create -s heroku-18 --region eu --json | jq -r '.name')
    $ /dev/shm/heroku/bin/heroku apps:info $appName

    Nowo utworzoną aplikację zobaczymy także w interfejsie webowym:

    https://dashboard.heroku.com/apps
    Tak naprawdę oczywiście wystarczyłoby heroku create (ewentualnie z własną nazwą tworzonej aplikacji), reszta powyższego polecenia przechwytuje nazwę nowo utworzonego projektu i zapisuję ją w zmiennej powłoki do późniejszego użycia.
  6. Wyślij kod aplikacji poprzez git na serwery Heroku:

    $ git push heroku master

    Aplikacja powinna być dostępna po podanym na końcu linkiem.

  7. Dodamy obsługę nowej ścieżki w naszej aplikacji. Zmieńmy plik src/main/java/com/example/Main.java:

    /*
     * Copyright 2002-2014 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package com.example;
    
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.Map;
    
    @Controller
    @SpringBootApplication
    public class Main {
    
      @Value("${spring.datasource.url}")
      private String dbUrl;
    
      @Autowired
      private DataSource dataSource;
    
      public static void main(String[] args) throws Exception {
        SpringApplication.run(Main.class, args);
      }
    
      @RequestMapping("/")
      String index() {
        return "index";
      }
    
      @RequestMapping("/hello/{name}")
      String hello(@PathVariable(value="name") String name) {
        return "hello";
      }
    
      @RequestMapping("/db")
      String db(Map<String, Object> model) {
        try (Connection connection = dataSource.getConnection()) {
          Statement stmt = connection.createStatement();
          stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ticks (tick timestamp)");
          stmt.executeUpdate("INSERT INTO ticks VALUES (now())");
          ResultSet rs = stmt.executeQuery("SELECT tick FROM ticks");
    
          ArrayList<String> output = new ArrayList<String>();
          while (rs.next()) {
            output.add("Read from DB: " + rs.getTimestamp("tick"));
          }
    
          model.put("records", output);
          return "db";
        } catch (Exception e) {
          model.put("message", e.getMessage());
          return "error";
        }
      }
    
      @Bean
      public DataSource dataSource() throws SQLException {
        if (dbUrl == null || dbUrl.isEmpty()) {
          return new HikariDataSource();
        } else {
          HikariConfig config = new HikariConfig();
          config.setJdbcUrl(dbUrl);
          return new HikariDataSource(config);
        }
      }
    
    }

    Dodajmy teraz plik src/main/resources/templates/hello.html:

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'db')}">
    
    <body>
      <div class="container">
        <h1>Hello <span th:text="${name}">name</span>!</h1>
      </div>
    </body>
    </html>
  8. Poniższe polecenia tworzą nowy commit i wysyłają zmiany do Heroku. Po krótkiej chwili aplikacja powinna zostać podmieniona:

    $ git add src/main/resources/templates/hello.html
    $ git commit -a -m 'Hello'
    $ git push heroku master
  9. Aplikację można też usunąć z linii poleceń:

    $ /dev/shm/heroku/bin/heroku apps:delete $appName

Aplikacja od zera w Pythonie:

  1. W tym zadaniu utworzymy aplikację od zera i uruchomimy ją w chmurze Heroku. Skorzystamy z gotowej aplikacji z zajęć 8 wyświetlającej losowe strony.

  2. Stwórz nowy katalog i wewnątrz niego utwórz puste repozytorium git-a:

    $ mkdir newApp
    $ cd newApp
    $ git init
  3. Stwórz w tym katalogu cztery pliki (layout.jinja2, table.jinja2, lipsum.jinja2 i main.py) o zawartości z zajęć 8.

  4. Tworzymy nową aplikację na Heroku i wysyłamy naszą aplikację:

    $ appName=$(/dev/shm/heroku/bin/heroku create -s heroku-18 --region eu --json | jq -r '.name')
    
    $ git add *
    $ git commit -m 'Pierwsza wersja'
    $ git push heroku master

    Ostatnia linijka się nie powiedzie, bo Heroku nie wie co zrobić z wysyłanym kodem.

  5. Heroku stara się automatycznie wykryć język aplikacji i odpowiednio ją skompilować/zbudować. Pisząc własną aplikację warto poczytać dokładnie jak to robi. Większość języków/frameworków ma pewne konwencje nazywania plików czy wyszczególniania zależności i wystarczy się do nich stosować, by Heroku obsłużyło je poprawnie.

    W szczególności obecność pliku requirements.txt jest sygnałem dla Heroku, że mamy do czynienia z aplikację napisaną w Pythonie a w tym pliku są zależności do zainstalowania przez pip. Tworzymy więc w naszym projekcie plik requirements.txt o zawartości:

    pyramid
    pyramid-jinja2
    pyramid-debugtoolbar
    faker
    uwsgi

    Następnie dodajemy go do repozytorium i wysyłamy do Heroku:

    git add requirements.txt
    git commit -m 'requirements.txt'
    git push heroku master

    Teraz po wysłaniu powinniśmy widzieć jak sciągane są zależności naszej aplikacji i na końcu zobaczymy link pod jakim nasza aplikacja działa, w przeglądarce zobaczymy jednak błąd.

  6. Możemy zobaczyć logi naszej aplikacji:

    $ /dev/shm/heroku/bin/heroku logs -a $appName

    Zobaczymy, że co prawda aplikacja się zbudowała, ale Heroku dalej nie wie jak ją uruchomić.

  7. Aby poinstruować Heroku jak zbudowaną aplikację uruchmić musimy utworzyć plik Procfile (uwaga na wielką literę) o zawartości:

    web: uwsgi --http-socket=:$PORT --die-on-term --module=main:app

    Mówimy w nim, że nasza aplikacja jest aplikacją webową i można ją uruchomić podanym poleceniem (to samo zadziałałoby lokalnie po utworzeniu odpowiedniego środowiska wirtualnego). Tutaj chcemy, by uWSGI nasłuchiwał na porcie o numerze pochodzącym ze zmiennej środowiskowej PORT (definiowanej przez Heroku).

    Po wysłaniu zmian do Heroku wszystko powinno już działać:

    git add Procfile
    git commit -m 'Procfile'
    git push heroku master
  8. Aplikację możemy znowu łatwo usunąć:

    $ /dev/shm/heroku/bin/heroku apps:delete $appName