FissionGo: как запускать Go-функции в Kubernetes

Kate

Administrator
Команда форума
Сегодня будем разбираться с FissionGo — фреймворком, который обещает избавить нас от работы с деплоями, контейнерами и YAML‑манифестами.

Допустим, нам нужно запустить функцию — небольшой кусок кода, который что‑то делает (парсит JSON, отвечает на запрос, обрабатывает webhook). План работы был бы примерно таким:

  1. Написать код
  2. Запихнуть его в контейнер
  3. Написать манифесты
  4. Настроить сервисы
  5. Объяснить Kubernetes, как всем этим управлять
  6. Надеяться, что всё не упадёт в проде
А 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 существует и на другие ЯП. Ознакомиться можно здесь.

 
Сверху