> For the complete documentation index, see [llms.txt](https://docs.facephi.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.facephi.com/docs.facephi-en/sdks/backend-sdk/ocr/installation/kubernetes_deployment.md).

# Deployment in Kubernetes

## 1. Introduction

In the following section we will explain how to install the service in a Kubernetes environment.

## 2. Manual deployment

### 2.1 Introduction

The OCR service can be deployed in Kubernetes with **kubectl**:

```bash
kubectl apply -f manifest.yaml
```

Using a file *manifest.yaml* similar to this one:

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: facephi-ocr-service
---

apiVersion: v1
kind: Secret
metadata:
  name: ocr-license-secret
  namespace: facephi-ocr-service
stringData:
  license.lic: |-
    {
      CONFIG_DIR=<provided by facephi>
      LICENSE_TYPE=<provided by facephi>
      LICENSE_BEHAVIOUR=<provided by facephi>
      LICENSE_ID=<provided by facephi>
      LICENSE_DATA=<provided by facephi>
      LICENSE_KEY=<provided by facephi>
    } 
---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ocr
  namespace: facephi-ocr-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ocr
  template:
    metadata:
      labels:
        app: ocr
    spec:
      volumes:
        - name: license-volume
          secret:
            secretName: ocr-license-secret
            defaultMode: 420
      containers:
        - name: facephi-ocr-service
          image: facephicorp.jfrog.io/docker-pro-fphi/facephi-ocr-service:2.4.6
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 6982
          env:
            # Optional JWT protection
            # - name: FACEPHI_OCR_REST_AUTH_ENABLED
            #   value: "true"
            # - name: FACEPHI_OCR_REST_AUTH_JWT_SECRET
            #   valueFrom:
            #     secretKeyRef:
            #       name: ocr-jwt-secret
            #       key: shared-secret
            # - name: FACEPHI_OCR_REST_AUTH_ACCEPT_AUTHORIZATION_HEADER
            #   value: "true"
            # - name: FACEPHI_OCR_REST_AUTH_ACCEPT_API_KEY_HEADER
            #   value: "false"
            # - name: FACEPHI_OCR_REST_AUTH_API_KEY_HEADER_NAME
            #   value: "x-api-key"
          volumeMounts:
            - name: license-volume
              readOnly: true
              mountPath: /service/license/license.lic
              subPath: license.lic
          resources:
            limits:
              cpu: 1024m
              memory: 2Gi
            requests:
              cpu: 512m
              memory: 1Gi
---

apiVersion: v1
kind: Service
metadata:
  name: ocr-service
  namespace: facephi-ocr-service
spec:
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 6982
  selector:
    app: ocr
  type: ClusterIP
---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ocr-ingress
  namespace: facephi-ocr-service
  annotations:
    konghq.com/strip-path: "true"
spec:
  ingressClassName: kong
  rules:
    - host: <your own dns for this service>
      http:
        paths:
          - path: /ocr
            pathType: Prefix
            backend:
              service:
                name: ocr-service
                port:
                  number: 80
---

```

It is important to have previously logged in to Artifactory or obtain the image **facephicorp.jfrog.io/docker-pro-fphi/facephi-ocr-service** and store it in a Docker image repository from which the cluster can download it.

### 2.2 Secret

It is necessary to declare a Kubernetes secret where the license is passed to Kubernetes.

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: ocr-license-secret
  namespace: facephi-ocr-service
stringData:
  # Write here your license content. E.g:
  license.lic: |-
    {
       "key":"XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
       "type":"NODE_ONLINE"
    }
```

### 2.3 Deployment

Remember to log in to Artifactory or download the Docker image beforehand.

#### 2.3.1 Volumes

It is necessary to add the volume with the client's license. By default, the path where the license file is stored is `/service/license`.

Once that secret has been created, the deployment will associate the volume at the corresponding path with the following lines:

```yaml
...
spec:
  ...
  template:
    ...
    spec:
      volumes:
        - name: license-volume
          secret:
            secretName: ocr-license-secret
            defaultMode: 420
        ...
      containers:
        ...
        - volumeMounts:
            - name: license-volume
              readOnly: true
              mountPath: /service/license/license.lic
              subPath: license.lic
```

`spec.volumes[0].secret.secretName` searches the namespace for the previously generated secret and stores it in a volume with the name `license-volume`. When mounting the `license-volume` the associated Secret is looked up, and `mountPath` it is set to the path where the file is stored, and you can specify a specific object from the secret with `subPath`, in this case the key `license`.

#### 2.3.2 Resources

After numerous load and stress tests, the following results have been obtained to ensure a response time of between 2700 and 3000 ms for identity documents.

| Service          | CPU   | Memory | Average time |
| ---------------- | ----- | ------ | ------------ |
| /api/v1/process/ | 512m  | 1Gi    | 5.4s         |
| /api/v1/process/ | 1024m | 2Gi    | 2.9s         |
| /api/v1/process/ | 2048m | 4Gi    | 2.8s         |

With these tests, the following configuration is established at the requests and limits level.

```yaml
spec:
  ...
  template:
    ...
    spec:
      ...
      containers:
        ...
          resources:
            limits:
              cpu: 2048m
              memory: 4Gi
            requests:
              cpu: 1024m
              memory: 2Gi
```

### 2.4 Service

When JWT authentication is enabled, `GET /api/v1/health` and `GET /api/v1/version` remain public and protected endpoints require a valid JWT. JWT startup settings are not exposed through `GET /api/v1/config` nor can they be updated through `POST /api/v1/config`.

#### 2.4.1 LoadBalancer

We note that we will configure a LoadBalancer with Kong in front to access the Facephi OCR Service. Please note that the service is exposed on port `80` and targets the Pod on `6982`.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: ocr-service
  namespace: facephi-ocr-service
spec:
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 6982
  selector:
    app: ocr
  type: ClusterIP
```

### 2.5 Ingress

We configure an Ingress in front to redirect requests from Kong to the service inside the Pod that we previously exposed on port 80.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ocr-ingress
  namespace: facephi-ocr-service
  annotations:
    konghq.com/strip-path: "true"
spec:
  ingressClassName: kong
  rules:
    - host: <your own dns for this service>
      http:
        paths:
          - path: /ocr
            pathType: Prefix
            backend:
              service:
                name: ocr-service
                port:
                  number: 80
```

## 3. Instance types

If the cluster is going to make use of Kubernetes HPA resources to scale the number of pods, it is recommended to take into account the maximum number of pods supported by each instance. The maximum number of pods considered appropriate according to the tests is shown in the following table.

| Instance type | CPU | Memory | OCR pod capacity |
| ------------- | --- | ------ | ---------------- |
| c5.xlarge     | 4   | 8      | 3                |
| c5.2xlarge    | 8   | 16     | 6                |
| c5.4xlarge    | 16  | 32     | 12               |

It is possible to add more OCR pods, but at the cost of response time.
