FissionGo: как запускать Go-функции в Kubernetes
- суббота, 8 февраля 2025 г. в 00:00:07
 
Привет, Хабр!
Сегодня будем разбираться с 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Напишем простейший обработчик 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 памяти — значит, держимся в рамках.
Запросы к функции — это здорово. Но если много запросов с одинаковыми данными, было бы неплохо не пересчитывать одно и то же каждый раз.
Допустим, есть функция, которая считает сложные данные. Сначала без кэша:
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 — продвинутые техники и корпоративные паттерны». Вы узнаете, как эффективно управлять зависимостями, использовать прокси и локальный кэш, а также обеспечивать безопасность проектов при помощи контроля сумм. Записывайтесь по ссылке.
Список всех бесплатных уроков можно посмотреть в календаре.