Привет, Хабр!
Сегодня будем разбираться с FissionGo — фреймворком, который обещает избавить нас от работы с деплоями, контейнерами и YAML‑манифестами.
Допустим, нам нужно запустить функцию — небольшой кусок кода, который что‑то делает (парсит JSON, отвечает на запрос, обрабатывает webhook). План работы был бы примерно таким:
Написать код
Запихнуть его в контейнер
Написать манифесты
Настроить сервисы
Объяснить Kubernetes, как всем этим управлять
Надеяться, что всё не упадёт в проде
А Fission убирает пункты 2–6. Просто пишем функцию, загружаем в Fission, привязываем HTTP‑триггер — и всё.
FissionGo — это Go‑специфичная среда для работы в Fission, которая компилирует Go‑код на лету, загружает его как плагин и исполняет в Kubernetes.
Установим
Первым делом — ставим Fission в Kubernetes.
kubectl create ns fission
kubectl apply -f https://github.com/fission/fission/releases/latest/download/fission.yaml
Ждём минутку, проверяем, что всё завелось:
fission version
Теперь добавляем Go‑окружение, чтобы можно было писать код.
fission environment create --name go \
--image ghcr.io/fission/go-env-1.23 \
--builder ghcr.io/fission/go-builder-1.23 --version 3
Первая Go-функция на Fission
Напишем простейший обработчик HTTP‑запросов.
package main
import (
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Привет, Fission!"))
}
Загружаем в Fission:
fission fn create --name hello --env go --src hello.go --entrypoint Handler
Привязываем HTTP‑триггер:
fission httptrigger create --method GET --url "/hello" --function hello
Проверяем:
curl http://$FISSION_ROUTER/hello
Ожидаемый результат:
Привет, Fission!
Никакого Docker, никакого kubectl apply -f deployment.yaml
, просто код → Fission → работающий HTTP‑эндпоинт.
Еще один пример
Напишем обработчик, который принимает JSON, меняет данные и отправляет ответ.
package main
import (
"encoding/json"
"net/http"
)
type Request struct {
Name string `json:"name"`
}
type Response struct {
Message string `json:"message"`
}
func Handler(w http.ResponseWriter, r *http.Request) {
var req Request
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
response := Response{Message: "Привет, " + req.Name + "!"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
Загружаем в fission:
zip -r json_handler.zip json_handler.go
fission fn create --name json-handler --env go --src json_handler.zip --entrypoint Handler
fission httptrigger create --method POST --url "/json" --function json-handler
curl -X POST http://$FISSION_ROUTER/json -d '{"name": "Хабр"}' -H "Content-Type: application/json"
Ожидаемый ответ:
{"message": "Привет, Хабр!"}
Всё так же просто.
Ограничиваем ресурсы
Когда вы запускаете серверлес‑функцию, по умолчанию она может занять столько ресурсов, сколько ей захочется. В тестовой среде это не страшно, но в проде кто‑то запустит тяжёлый запрос, Kubernetes выделит под него кучу CPU, и... всё. У других сервисов просто не останется ресурсов.
Используем опции --mincpu
, --maxcpu
, --minmemory
, --maxmemory
. Они задают гарантированный минимум и максимальный предел использования ресурсов.
fission fn create --name optimized --env go --src function.zip --entrypoint Handler \
--mincpu 100 --maxcpu 500 --minmemory 256 --maxmemory 1024
--mincpu 100
— минимальный лимит 100 millicores CPU (0.1 ядра). Даже если кластер загружен, Fission выделит минимум 0.1 ядра на выполнение функции.
--maxcpu 500
— если сервер загружен не полностью, функция может использовать до 500 millicores (0.5 ядра).
--minmemory 256
— минимальный гарантированный объём памяти — 256 MB.
--maxmemory 1024
— максимальный объём памяти — 1024 MB (1 GB).
Есть командаkubectl top pod
, чтобы увидеть, сколько CPU и RAM реально использует функция:
kubectl top pod -l functionName=optimized
Вывод примерно такой:
NAME CPU(cores) MEMORY(bytes)
optimized-xyz123 250m 500Mi
Окей, 250 millicores CPU и 500 MB памяти — значит, держимся в рамках.
Кэшируем ответы через Redis
Запросы к функции — это здорово. Но если много запросов с одинаковыми данными, было бы неплохо не пересчитывать одно и то же каждый раз.
Допустим, есть функция, которая считает сложные данные. Сначала без кэша:
package main
import (
"fmt"
"net/http"
"time"
)
func Handler(w http.ResponseWriter, r *http.Request) {
time.Sleep(2 * time.Second) // Имитируем тяжелый расчёт
fmt.Fprintf(w, "Рассчитали ответ!")
}
Запускаем 10 запросов, и все ждут по 2 секунды.
Используем Redis, чтобы кешировать результат:
package main
import (
"fmt"
"github.com/go-redis/redis/v8"
"context"
"net/http"
"time"
)
var ctx = context.Background()
var client = redis.NewClient(&redis.Options{
Addr: "redis:6379",
})
func Handler(w http.ResponseWriter, r *http.Request) {
cached, err := client.Get(ctx, "result").Result()
if err == nil {
fmt.Fprintf(w, "Из кеша: %s", cached)
return
}
// Эмуляция тяжелого расчёта
time.Sleep(2 * time.Second)
result := "Рассчитали ответ!"
client.Set(ctx, "result", result, 10*time.Minute) // Кладём в кэш на 10 минут
fmt.Fprintf(w, result)
}
Теперь первый запрос посчитает результат и положит его в Redis. Остальные просто будут его брать из кэша. А еще у Redis можно настроить автоочистку кеша, чтобы старые данные не занимали память.
Если у вас Kubernetes, но вам лень возиться с контейнерами и манифестами, FissionGo — хороший вариант.
Fission существует и на другие ЯП. Ознакомиться можно здесь.
Напоследок рекомендую обратить внимание на открытый урок «Управление зависимостями в Go — продвинутые техники и корпоративные паттерны». Вы узнаете, как эффективно управлять зависимостями, использовать прокси и локальный кэш, а также обеспечивать безопасность проектов при помощи контроля сумм. Записывайтесь по ссылке.
Список всех бесплатных уроков можно посмотреть в календаре.