Fullstack-приложение на Go c подключением к PostgreSQL¶
Чтобы развернуть основное приложение в Amvera, нужно выполнить следующие простые шаги:
Открываем страницу https://cloud.amvera.ru/projects
Нажимаем кнопку Создать и выбираем тип сервиса приложение
Выгружаем все файлы (можно через git, а можно через интерфейс). Убедитесь, что вы выгрузили все нужные файлы. Для проекта из примера это:
go.mod (обязательно)
go.sum (обязательно)
main.go (обязательно)
Dockerfile (обязательно)
static/index.html (если используете)
static/script.js (если используете)
static/styles.css (если используете)
amvera.yml (необязательно)
После этого начнется сборка и развертывание приложения. Дождитесь появления статуса «Успешно развернуто».
Рассмотрим процесс подробнее.
Видеопример деплоя Go-приложения¶
Создадим простое web приложение на языке программирования Go, где можно будет оставлять и читать цитаты. Для их хранения будем использовать СУБД PostgreSQL.¶
Директория приложения имеет следующую структуру:
└─ code/
├── static
│ ├── styles.css
│ ├── script.js
│ └── index.html
├── amvera.yml
├── main.go
├── go.sum
├── go.mod
└── Dockerfile
Код статических файлов доступен в конце страницы.
Конфигурация¶
Amvera.yml¶
Написать yaml файл можно как самостоятельно, так и воспользоваться нашим генератором yaml, перейдя по ссылке, либо заполнить в разделе «Конфигурация» личного кабинета.
Пример файла amvera.yml при использовании только amvera.yml:
meta:
environment: golang
toolchain:
name: go
version: 1.22
build:
image: golang:1.22
run:
image: golang:1.22
persistenceMount: /data
containerPort: 80
Пример файла amvera.yml при использовании вместе с Dockerfile:
meta:
environment: docker
toolchain: docker
build:
dockerfile: Dockerfile
skip: false
run:
persistenceMount: /data
containerPort: "80"
Важно: сохранять изменяемые файлы (база данных и т.д.) именно в постоянное хранилище /data. Это позволит избежать их потери при пересборке. Папка Data в коде и постоянное хранилище /data - разные директории.
Рассмотрим альтернативный способ задания конфигурации - через Dockerfile.
Dockerfile¶
Замечание: если вы используете Dockerfile, то конфигурационный файл amvera.yml в большинстве случаев можно не добавлять.
Шаги:¶
Создаем Dockerfile в директории с проектом.
В Dockerfile указываем базовый образ с названием builder:
FROM golang:1.21.1 AS builder
вместо 1.21.1 можете указать любую другую версию, которая вам нужна.
Устанавливаем рабочую директорию:
WORKDIR /app
Копируем файлы main.go, go.mod и go.sum в текущую директорию рабочего каталога:
COPY main.go go.mod go.sum ./
Выполняем сборку проекта и создаем исполняемый файл server:
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o server
Используем образ Alpine Linux (это уменьшит размер конечного образа и улучшит его производительность):
FROM alpine:latest
Копируем исполняемый файл server, собранный в предыдущем образе, в текущую директорию рабочего каталога:
COPY --from=builder /app/server ./
Копируем статические файлы внутрь контейнера:
COPY static/ ./static/
Открываем порт 80 для внешних подключений:
EXPOSE 80
Добавляем команду для запуска приложения:
CMD ["./server", "--port", "80"]
Получившийся Dockerfile:
FROM golang:1.21.1 AS builder
WORKDIR /app
COPY main.go go.mod go.sum ./
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o server
FROM alpine:latest
COPY --from=builder /app/server ./
COPY static/ ./static/
EXPOSE 80
CMD ["./server", "--port", "80"]
Зависимости (go.mod и sum.go)¶
Инициализируем новый модуль для управления зависимостями. Для этого выполняем следующую команду, которая создаст файл go.mod:
$ go mod init main
Замечание: вместо main можно написать произвольную строку
Остаётся выполнить ещё одну команду, которая добавит по две записи на каждую зависимость и создаст файл go.sum:
go mod tidy
Развертывание СУБД (PostgreSQL)¶
Базу данных нужно развернуть как отдельное приложение, а затем можно будет подключаться к ней из основного приложения. Подробная инструкция доступна по ссылке.
Создание проекта в Amvera¶
Последний шаг - развернуть само приложение. В файле main.go содержится основной код и выполняется подключение к базе данных. Не забудьте поменять параметры для подключения к базе данных на те, которые вы использовали в прошлом шаге при создании базы данных на Amvera:
user = Имя пользователя
password = Пароль пользователя
dbname = Имя создаваемой БД
параметр host можно найти на странице Инфо вашего PostgreSQL проекта (например, amvera-username-cnpg-appname-rw)
main.go:
package main
import (
"database/sql"
"encoding/json"
"log"
"net/http"
"strconv"
_ "github.com/lib/pq"
)
// Укажите те значения, которые задавали при создании БД на Amvera Cloud
const (
host = "amvera-nskripko-cnpg-godb-rw"
port = 5432
user = "nick"
password = "href239"
dbname = "godb"
)
func main() {
portStr := strconv.Itoa(port)
dbinfo := "host=" + host + " port=" + portStr + " user=" + user + " password=" + password + " dbname=" + dbname + " sslmode=disable"
db, err := sql.Open("postgres", dbinfo)
if err != nil {
log.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS quotes (
id SERIAL PRIMARY KEY,
quote TEXT NOT NULL
)`)
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/quotes", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
rows, err := db.Query("SELECT quote FROM quotes")
if err != nil {
log.Println("Error querying database:", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
defer rows.Close()
var quotes []string
for rows.Next() {
var quote string
if err := rows.Scan("e); err != nil {
log.Println("Error scanning rows:", err)
continue
}
quotes = append(quotes, quote)
}
json.NewEncoder(w).Encode(quotes)
})
http.HandleFunc("/addquote", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var data struct {
Quote string `json:"quote"`
}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
_, err := db.Exec("INSERT INTO quotes (quote) VALUES ($1)", data.Quote)
if err != nil {
log.Println("Error inserting quote into database:", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
})
fs := http.FileServer(http.Dir("static"))
http.Handle("/", fs)
log.Fatal(http.ListenAndServe(":80", nil))
}
:warning: Код является демонстрационным примером и мы настоятельно не рекомендуем указывать логин и пароль для подключения к базе данных в коде. Используйте переменные окружения (секреты)!
Проверка работоспособности¶
Переходим в настройки проекта и активируем доменное имя.
Теперь можно перейти по данному URL и откроется наше приложение.
Если что-то не работает, рекомендуем ознакомиться с логами Сборки и Приложения.
Поздравляем, вы успешно создали свое первое приложение в Amvera!
Код статических файлов из примера¶
static/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quote Board</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Quote Board</h1>
<form id="quoteForm">
<input type="text" id="quoteInput" placeholder="Enter your quote" required>
<button type="submit">Submit</button>
</form>
<div id="quoteList"></div>
</div>
<script src="script.js"></script>
</body>
</html>
static/styles.css:
body {
font-family: Arial, sans-serif;
}
.container {
max-width: 600px;
margin: 50px auto;
padding: 0 20px;
}
input[type="text"] {
width: calc(100% - 80px);
padding: 10px;
margin-right: 10px;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
#quoteList {
margin-top: 20px;
}
static/script.js:
document.addEventListener('DOMContentLoaded', () => {
const quoteForm = document.getElementById('quoteForm');
const quoteInput = document.getElementById('quoteInput');
const quoteList = document.getElementById('quoteList');
// Function to fetch quotes from the server and display them
const fetchQuotes = async () => {
try {
const response = await fetch('/quotes');
const quotes = await response.json();
// Clear previous quotes
quoteList.innerHTML = '';
// Append new quotes to the list
quotes.forEach(quote => {
const quoteItem = document.createElement('div');
quoteItem.textContent = quote;
quoteList.appendChild(quoteItem);
});
} catch (error) {
console.error('Error fetching quotes:', error);
}
};
// Fetch initial quotes when the page loads
fetchQuotes();
// Submit quote form
quoteForm.addEventListener('submit', async event => {
event.preventDefault();
const newQuote = quoteInput.value.trim();
if (newQuote === '') {
alert('Please enter a quote.');
return;
}
try {
// Send the new quote to the server
await fetch('/addquote', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ quote: newQuote })
});
// Clear the input field
quoteInput.value = '';
// Fetch and display updated quotes
fetchQuotes();
} catch (error) {
console.error('Error adding quote:', error);
}
});
});
Важно
Сохраняйте файлы БД и иные изменяемые данные в постоянное хранилище, чтобы избежать их потери при обновлении проекта, когда производится «откат» папки код до состояния обновления репозитория. Папка data в корне проекта и директория /data, это разные директории.
Проверить, что сохранение идет в /data, можно зайдя в папку «data» на странице «Репозиторий».
Важно
Чтобы избежать ошибки 502, измените в вашем коде host 127.0.0.1 (или подобный localhost) на 0.0.0.0, и пропишите в конфигурации порт, который слушает ваше приложение (пример - 8080).
Если у вас не получается развернуть проект¶
Напишите наблюдаемую вами симптоматику на support@amvera.ru с указанием вашего имени пользователя и названия проекта, мы постараемся вам помочь.