Zarejestruj się w usłudze Heroku:
https://signup.heroku.com/
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
Zaloguj się w zainstalowanym narzędziu:
$ /dev/shm/heroku/bin/heroku login
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
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
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.
Wyślij kod aplikacji poprzez git
na serwery Heroku:
$ git push heroku master
Aplikacja powinna być dostępna po podanym na końcu linkiem.
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>
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
Aplikację można też usunąć z linii poleceń:
$ /dev/shm/heroku/bin/heroku apps:delete $appName
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.
Stwórz nowy katalog i wewnątrz niego utwórz puste repozytorium git-a:
$ mkdir newApp
$ cd newApp
$ git init
Stwórz w tym katalogu cztery pliki (layout.jinja2
, table.jinja2
, lipsum.jinja2
i main.py
) o zawartości z zajęć 8.
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.
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.
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ć.
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
Aplikację możemy znowu łatwo usunąć:
$ /dev/shm/heroku/bin/heroku apps:delete $appName