Tempo de execução personalizado
Cenários
Um tempo de execução executa o código de uma função, lê o nome do manipulador de uma variável de ambiente e lê eventos de invocação das API de tempo de execução do FunctionGraph. O tempo de execução passa os dados do evento para o manipulador da função e retorna a resposta do manipulador para FunctionGraph.
O FunctionGraph oferece suporte a tempos de execução personalizados. Você pode usar um arquivo executável chamado bootstrap para incluir um runtime no pacote de implantação da função. O tempo de execução executa o método manipulador da função quando a função é invocada.
Seu tempo de execução é executado no ambiente de execução do FunctionGraph. Pode ser um script shell ou um arquivo executável binário compilado no Linux.
Após a programação, basta empacotar seu código em um arquivo ZIP (Java, Node.js, Python e Go) ou arquivo JAR (Java) e fazer o upload do arquivo para FunctionGraph para execução. Ao criar um arquivo ZIP, coloque o arquivo do manipulador sob o diretório root para garantir que seu código possa ser executado normalmente após ser descompactado.
Se você editar código em Go, compacte o arquivo compilado e verifique se o nome do arquivo de biblioteca dinâmica é consistente com o nome do plug-in do manipulador. Por exemplo, se o nome do arquivo de biblioteca dinâmica for testplugin.so, defina o nome do manipulador como testplugin.Handler.
Bootstrap do arquivo de tempo de execução
Se houver um arquivo chamado bootstrap no pacote de implantação da função, o FunctionGraph executará esse arquivo. Se o arquivo bootstrap não for encontrado ou não for executável, sua função retornará um erro quando for invocada.
O código de tempo de execução é responsável por concluir as tarefas de inicialização. Ele processa eventos de invocação em um loop até que seja encerrado.
As tarefas de inicialização são executadas uma vez para cada instância da função para preparar o ambiente para manipulação de invocações.
As API de tempo de execução
O FunctionGraph fornece as API de tempo de execução HTTP para receber eventos de chamada de função e retornar dados de resposta no ambiente de execução.
- Próxima invocação
Método – Get
Caminho – http://$RUNTIME_API_ADDR/v1/runtime/invocation/request
Essa API é usada para recuperar um evento de invocação. O corpo da resposta contém os dados do evento. A tabela a seguir descreve dados adicionais sobre a invocação contida no cabeçalho da resposta.
Tabela 1 Informações de cabeçalho de resposta Parâmetro
Descrição
X-Cff-Request-Id
Solicitar ID.
X-CFF-Access-Key
AK da conta. Uma agência deve ser configurada para a função se essa variável for usada.
X-CFF-Auth-Token
Token da conta. Uma agência deve ser configurada para a função se essa variável for usada.
X-CFF-Invoke-Type
Tipo de invocação da função.
X-CFF-Secret-Key
SK da conta. Uma agência deve ser configurada para a função se essa variável for usada.
X-CFF-Security-Token
Token de segurança da conta. Uma agência deve ser configurada para a função se essa variável for usada.
- Resposta de invocação
Método – POST
Caminho – http://$RUNTIME_API_ADDR/v1/runtime/invocation/response/$REQUEST_ID
Essa API é usada para enviar uma resposta de invocação bem-sucedida para a FunctionGraph. Depois que o tempo de execução invoca o manipulador da função, ele publica a resposta da função no caminho de resposta da invocação.
- Erro de invocação
Método – POST
Caminho – http://$RUNTIME_API_ADDR/v1/runtime/invocation/error/$REQUEST_ID
$REQUEST_ID é o valor da variável X-Cff-Request-Id no cabeçalho de uma resposta de recuperação de evento. Para obter mais informações, consulte Tabela 1.
$RUNTIME_API_ADDR é uma variável de ambiente do sistema. Para obter mais informações, consulte Tabela 2.
Essa API é usada para enviar uma resposta de invocação de erro para a FunctionGraph. Depois que o tempo de execução invoca o manipulador da função, ele publica a resposta da função no caminho de resposta da invocação.
Variáveis de ambiente de tempo de execução
Você pode usar variáveis de ambiente personalizadas e de tempo de execução no código de função. A tabela a seguir lista as variáveis de ambiente de tempo de execução usadas no ambiente de execução do FunctionGraph.
Chave |
Descrição |
---|---|
RUNTIME_PROJECT_ID |
ID do projeto |
RUNTIME_FUNC_NAME |
Nome da função |
RUNTIME_FUNC_VERSION |
Versão da função |
RUNTIME_PACKAGE |
Aplicativo ao qual a função pertence |
RUNTIME_HANDLER |
Manipulador de funções |
RUNTIME_TIMEOUT |
Duração do timeout da função |
RUNTIME_USERDATA |
Valor passado por uma variável de ambiente |
RUNTIME_CPU |
Número de núcleos de CPU alocados |
RUNTIME_MEMORY |
Memória alocada |
RUNTIME_CODE_ROOT |
Diretório que armazena o código da função |
RUNTIME_API_ADDR |
Endereço IP do host e porta de uma API de tempo de execução personalizada |
O valor de uma variável de ambiente personalizada pode ser recuperado da mesma forma que o valor de uma variável de ambiente FunctionGraph.
Exemplo
Este exemplo contém um arquivo chamado bootstrap. O arquivo é implementado em Bash.
O tempo de execução carrega o script de função do pacote de implantação usando duas variáveis.
O arquivo bootstrap é o seguinte:
#!/bin/sh set -o pipefail #Processing requests loop while true do HEADERS="$(mktemp)" # Get an event EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://$RUNTIME_API_ADDR/v1/runtime/invocation/request") # Get request id from response header REQUEST_ID=$(grep -Fi x-cff-request-id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) if [ -z "$REQUEST_ID" ]; then continue fi # Process request data RESPONSE="Echoing request: '$EVENT_DATA'" # Put response curl -X POST "http://$RUNTIME_API_ADDR/v1/runtime/invocation/response/$REQUEST_ID" -d "$RESPONSE" done
Após carregar o script, o runtime processa eventos de invocação em um loop até que ele seja encerrado. Ele usa a API para recuperar eventos de invocação do FunctionGraph e envia as respostas para FunctionGraph.
Para obter a ID da solicitação, o runtime salva o cabeçalho da resposta da API em um arquivo temporário e lê a ID da solicitação no campo do cabeçalho x-cff-request-id. O tempo de execução processa os dados do evento recuperados e envia uma resposta de volta para FunctionGraph.
A seguir, um exemplo de código-fonte em Go. Ele pode ser executado somente após a compilação.
package main import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "log" "net" "net/http" "os" "strings" "time" ) var ( getRequestUrl = os.ExpandEnv("http://${RUNTIME_API_ADDR}/v1/runtime/invocation/request") putResponseUrl = os.ExpandEnv("http://${RUNTIME_API_ADDR}/v1/runtime/invocation/response/{REQUEST_ID}") putErrorResponseUrl = os.ExpandEnv("http://${RUNTIME_API_ADDR}/v1/runtime/invocation/error/{REQUEST_ID}") requestIdInvalidError = fmt.Errorf("request id invalid") noRequestAvailableError = fmt.Errorf("no request available") putResponseFailedError = fmt.Errorf("put response failed") functionPackage = os.Getenv("RUNTIME_PACKAGE") functionName = os.Getenv("RUNTIME_FUNC_NAME") functionVersion = os.Getenv("RUNTIME_FUNC_VERSION") client = http.Client{ Transport: &http.Transport{ DialContext: (&net.Dialer{ Timeout: 3 * time.Second, }).DialContext, }, } ) func main() { // main loop for processing requests. for { requestId, header, payload, err := getRequest() if err != nil { time.Sleep(50 * time.Millisecond) continue } result, err := processRequestEvent(requestId, header, payload) err = putResponse(requestId, result, err) if err != nil { log.Printf("put response failed, err: %s.", err.Error()) } } } // event processing function func processRequestEvent(requestId string, header http.Header, evtBytes []byte) ([]byte, error) { log.Printf("processing request '%s'.", requestId) result := fmt.Sprintf("function: %s:%s:%s, request id: %s, headers: %+v, payload: %s", functionPackage, functionName, functionVersion, requestId, header, string(evtBytes)) var event FunctionEvent err := json.Unmarshal(evtBytes, &event) if err != nil { return (&ErrorMessage{ErrorType: "invalid event", ErrorMessage: "invalid json formated event"}).toJsonBytes(), err } return (&APIGFormatResult{StatusCode: 200, Body: result}).toJsonBytes(), nil } func getRequest() (string, http.Header, []byte, error) { resp, err := client.Get(getRequestUrl) if err != nil { log.Printf("get request error, err: %s.", err.Error()) return "", nil, nil, err } defer resp.Body.Close() // get request id from response header requestId := resp.Header.Get("X-CFF-Request-Id") if requestId == "" { log.Printf("request id not found.") return "", nil, nil, requestIdInvalidError } payload, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("read request body error, err: %s.", err.Error()) return "", nil, nil, err } if resp.StatusCode != 200 { log.Printf("get request failed, status: %d, message: %s.", resp.StatusCode, string(payload)) return "", nil, nil, noRequestAvailableError } log.Printf("get request ok.") return requestId, resp.Header, payload, nil } func putResponse(requestId string, payload []byte, err error) error { var body io.Reader if payload != nil && len(payload) > 0 { body = bytes.NewBuffer(payload) } url := "" if err == nil { url = strings.Replace(putResponseUrl, "{REQUEST_ID}", requestId, -1) } else { url = strings.Replace(putErrorResponseUrl, "{REQUEST_ID}", requestId, -1) } resp, err := client.Post(strings.Replace(url, "{REQUEST_ID}", requestId, -1), "", body) if err != nil { log.Printf("put response error, err: %s.", err.Error()) return err } defer resp.Body.Close() responsePayload, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("read request body error, err: %s.", err.Error()) return err } if resp.StatusCode != 200 { log.Printf("put response failed, status: %d, message: %s.", resp.StatusCode, string(responsePayload)) return putResponseFailedError } return nil } type FunctionEvent struct { Type string `json:"type"` Name string `json:"name"` } type APIGFormatResult struct { StatusCode int `json:"statusCode"` IsBase64Encoded bool `json:"isBase64Encoded"` Headers map[string]string `json:"headers,omitempty"` Body string `json:"body,omitempty"` } func (result *APIGFormatResult) toJsonBytes() []byte { data, err := json.MarshalIndent(result, "", " ") if err != nil { return nil } return data } type ErrorMessage struct { ErrorType string `json:"errorType"` ErrorMessage string `json:"errorMessage"` } func (errMsg *ErrorMessage) toJsonBytes() []byte { data, err := json.MarshalIndent(errMsg, "", " ") if err != nil { return nil } return data }
Tabela 3 descreve as variáveis de ambiente usadas no código anterior.