diff --git a/.dockerignore b/.dockerignore index 90fc797..81a0c85 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,18 @@ +.git/ +.github/ +.vscode/ +bin/ +charts/ +docker/ +images/ +openapi/ **/.gitignore **/*Dockerfile* .dockerignore .editorconfig .gitattributes +.gitignore .prettierrc.yaml -LICENSE -NOTICE +Makefile README.md +*.yaml diff --git a/README.md b/README.md index 8c29e34..3001190 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,42 @@ -# Virtual Kubelet Provider for SaladCloud +# SaladCloud Virtual Kubelet Provider [![License](https://img.shields.io/github/license/SaladTechnologies/virtual-kubelet-saladcloud)](./LICENSE) [![CI Workflow](https://github.com/SaladTechnologies/virtual-kubelet-saladcloud/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/SaladTechnologies/virtual-kubelet-saladcloud/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/SaladTechnologies/virtual-kubelet-saladcloud)](https://goreportcard.com/report/github.com/SaladTechnologies/virtual-kubelet-saladcloud) Salad's Virtual Kubelet (VK) provider for SaladCloud enables running Kubernetes (K8s) pods as container group deployments. -To Setup the project -1. Clone the repo command -```bash -git clone -``` -2. Install the dependencies -```bash -go mod download -``` -3. Build the project -```bash -go build -o virtual-kubelet ./cmd/virtual-kubelet/main.go -``` -4. Run the project -```bash -go run main.go --nodename {valid_node_name} --sce-project-name {projectName} --sce-organization-name {organizationName} --sce-api-key {api-key} --kubeconfig {kubeconfig} -``` - -## Prerequisites -You should have valid configuration required to run the project - -go to portal.salad.io and create a project and get the api-key and organization name -set the kubeconfig to the valid kubeconfig file - - -1. Valid ApiKey and OrganizationName -2. Valid Kubeconfig -3. Valid NodeName -4. Valid ProjectName +## Development +Follow the steps below to get started with local development. + +### Prerequisites + +- [Git](https://git-scm.com/downloads) +- [Go](https://go.dev/dl) +- [Docker Desktop](https://docs.docker.com/get-docker/) with a [local Kubernetes cluster](https://docs.docker.com/desktop/kubernetes/) + +### Getting Started + +1. Clone the repository. + + ```sh + git clone https://github.com/SaladTechnologies/virtual-kubelet-saladcloud.git + ``` + +2. Restore the dependencies. + + ```sh + go mod download + go mod verify + ``` + +3. Build the project. + + ```sh + go build -o ./bin/virtual-kubelet ./cmd/virtual-kubelet/main.go + ``` + +4. Run the project. + + ```sh + ./bin/virtual-kubelet --sce-api-key {apiKey} --sce-project-name {projectName} --sce-organization-name {organizationName} + ``` diff --git a/charts/virtual-kubelet/Chart.yaml b/charts/virtual-kubelet/Chart.yaml new file mode 100644 index 0000000..3e995ca --- /dev/null +++ b/charts/virtual-kubelet/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +description: Deploy containers to SaladCloud from your Kubernetes cluster using a virtual node. +home: https://github.com/SaladTechnologies/virtual-kubelet-saladcloud +icon: https://raw.githubusercontent.com/SaladTechnologies/virtual-kubelet-saladcloud/main/images/salad-logo-500x500.png +keywords: + - gpu + - kubelet + - kubernetes + - salad + - saladcloud + - virtual-kubelet + - virtual-kubelet-provider + - vk +kubeVersion: ">=v1.26.0-0" +maintainers: + - email: dev@salad.com + name: Salad Chefs + url: https://salad.com/ +name: virtual-kubelet-saladcloud +sources: + - https://github.com/SaladTechnologies/virtual-kubelet-saladcloud +type: application +version: 0.1.0 diff --git a/charts/virtual-kubelet/README.md b/charts/virtual-kubelet/README.md new file mode 100644 index 0000000..0a6ec44 --- /dev/null +++ b/charts/virtual-kubelet/README.md @@ -0,0 +1,98 @@ +# SaladCloud Virtual Kubelet Provider + + + +![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) + +Deploy containers to SaladCloud from your Kubernetes cluster using a virtual node. + +## Installation + +Follow the steps below to get started with the SaladCloud Virtual Kubelet Provider. + +### Prerequisites + +- [Kubernetes 1.26+](https://kubernetes.io/docs/setup/) +- [Helm v3+](https://helm.sh/docs/intro/quickstart/#install-helm) + +### Installing the chart + +1. Clone the repository. + + ```sh + git clone https://github.com/SaladTechnologies/virtual-kubelet-saladcloud.git + ``` + +2. Install the chart. + + ```shell + helm install \ + --create-namespace \ + --namespace saladcloud + --set salad.apiKey=$SCE_API_KEY \ + --set salad.organizationName=$SCE_ORGANIZATION_NAME \ + --set salad.projectName=$SCE_PROJECT_NAME \ + mysaladcloud ./charts/virtual-kubelet + ``` + +3. Verify that the virtual node is available. + + ```sh + kubectl get nodes + ``` + +
+ Results + + ```shell + NAME STATUS ROLES AGE VERSION + saladcloud-node Ready agent 1m v1.0.0 + ``` + +
+ +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalLabels | object | `{}` | The collection of additional labels applied to all resources. | +| clusterRoleNameOverride | string | `""` | When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.fullName` value in the `ClusterRoleBinding` resource. | +| fullNameOverride | string | `""` | When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.fullName` value. | +| imagePullSecrets | list | `[]` | The list of `Secret` names containing the registry credentials. | +| name | string | `"virtual-kubelet"` | The SaladCloud Virtual Kubelet Provider name. | +| nameOverride | string | `""` | When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.name` value. | +| provider.deploymentAnnotations | object | `{}` | The collection of annotations applied to the `Deployment` resource. | +| provider.image.digest | string | `""` | The SaladCloud Virtual Kubelet Provider image digest. When set to a non-empty value, the image is pulled by digest (ignore the tag value). | +| provider.image.pullPolicy | string | `"IfNotPresent"` | The `imagePullPolicy` for the SaladCloud Virtual Kubelet Provider image. May be `IfNotPresent`, `Always`, or `Never`. | +| provider.image.repository | string | `"ghcr.io/saladtechnologies/virtual-kubelet-saladcloud"` | The SaladCloud Virtual Kubelet Provider image repository URI. | +| provider.image.tag | string | `""` | The SaladCloud Virtual Kubelet Provider image tag. | +| provider.logLevel | string | `"warn"` | The log level. May be `error`, `warn`, `info`, `debug`, or `trace`. | +| provider.nodeSelector | object | `{}` | The collection of labels used to assign the SaladCloud Virtual Kubelet Provider pod. | +| provider.nodename | string | `"saladcloud-node"` | The SaladCloud Virtual Kubelet Provider node name. | +| provider.podAnnotations | object | `{}` | The collection of annotations applied to the `Pod` resources created by the deployment. | +| provider.taintEnabled | bool | `true` | The flag indicating whether the SaladCloud Virtual Kubelet Provider node is tainted. | +| rbac.annotations | object | `{}` | The collection of annotations applied to the `ClusterRoleBinding` resource. | +| rbac.clusterRoleName | string | `"cluster-admin"` | The name of the `ClusterRole` resource. | +| rbac.create | bool | `true` | The flag indicating whether the `ClusterRoleBinding` resource should be created. | +| salad.apiKey | string | `""` | The SaladCloud API key. | +| salad.organizationName | string | `""` | The SaladCloud organization name. | +| salad.projectName | string | `""` | The SaladCloud project name. | +| serviceAccount.annotations | object | `{}` | The collection of annotations applied to the `ServiceAccount` resource. | +| serviceAccount.create | bool | `true` | The flag indicating whether the `ServiceAccount` resources should be created. | +| serviceAccountNameOverride | string | `""` | When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.fullName` value in the `ServiceAccount` resource. | + +## Requirements + +Kubernetes: `>=v1.26.0-0` + + + +## Source Code + +* + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Salad Chefs | | | diff --git a/charts/virtual-kubelet/README.md.gotmpl b/charts/virtual-kubelet/README.md.gotmpl new file mode 100644 index 0000000..ed1eba7 --- /dev/null +++ b/charts/virtual-kubelet/README.md.gotmpl @@ -0,0 +1,60 @@ +# SaladCloud Virtual Kubelet Provider + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.typeBadge" . }} {{ template "chart.versionBadge" . }} + +{{ template "chart.description" . }} + +## Installation + +Follow the steps below to get started with the SaladCloud Virtual Kubelet Provider. + +### Prerequisites + +- [Kubernetes 1.26+](https://kubernetes.io/docs/setup/) +- [Helm v3+](https://helm.sh/docs/intro/quickstart/#install-helm) + +### Installing the chart + +1. Clone the repository. + + ```sh + git clone https://github.com/SaladTechnologies/virtual-kubelet-saladcloud.git + ``` + +2. Install the chart. + + ```shell + helm install \ + --create-namespace \ + --namespace saladcloud + --set salad.apiKey=$SCE_API_KEY \ + --set salad.organizationName=$SCE_ORGANIZATION_NAME \ + --set salad.projectName=$SCE_PROJECT_NAME \ + mysaladcloud ./charts/virtual-kubelet + ``` + +3. Verify that the virtual node is available. + + ```sh + kubectl get nodes + ``` + +
+ Results + + ```shell + NAME STATUS ROLES AGE VERSION + saladcloud-node Ready agent 1m v1.0.0 + ``` + +
+ +{{ template "chart.valuesSection" . }} + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.maintainersSection" . }} diff --git a/charts/virtual-kubelet/templates/NOTES.txt b/charts/virtual-kubelet/templates/NOTES.txt new file mode 100644 index 0000000..53d7fad --- /dev/null +++ b/charts/virtual-kubelet/templates/NOTES.txt @@ -0,0 +1,21 @@ +The SaladCloud virtual node is deploying to your cluster. + +To verify the deployment, run: + + kubectl --namespace={{ .Release.Namespace }} get pods --selector='app.kubernetes.io/name={{ include "virtual-kubelet-saladcloud.name" . }},app.kubernetes.io/instance={{ .Release.Name }}' + +To verify the virtual node, run: + + kubectl get node {{ .Values.provider.nodename }} + +{{- if lt .Capabilities.KubeVersion.Minor "26" }} +------------------------------------------------------------------------------------- +WARNING - Running on unsupported Kubernetes version "1.{{.Capabilities.KubeVersion.Minor}}". This is supported and tested on Kubernetes "1.26" or higher. +------------------------------------------------------------------------------------- +{{- end }} + +Learn more about SaladCloud and running workloads: + + - https://docs.salad.com/ + - https://portal.salad.com/ + - https://salad.com/ diff --git a/charts/virtual-kubelet/templates/_helpers.tpl b/charts/virtual-kubelet/templates/_helpers.tpl new file mode 100644 index 0000000..e829b99 --- /dev/null +++ b/charts/virtual-kubelet/templates/_helpers.tpl @@ -0,0 +1,85 @@ +{{/* +Return the chart name, formatted for use as a label. +*/}} +{{- define "virtual-kubelet-saladcloud.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return the chart name and version, formatted for use as a label. +*/}} +{{- define "virtual-kubelet-saladcloud.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return the common labels used by components. +*/}} +{{- define "virtual-kubelet-saladcloud.labels" -}} +app.kubernetes.io/component: kubelet +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: {{ include "virtual-kubelet-saladcloud.name" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion }} +helm.sh/chart: {{ include "virtual-kubelet-saladcloud.chart" . }} +{{ include "virtual-kubelet-saladcloud.matchLabels" . }} +{{- with .Values.additionalLabels -}} +{{ toYaml . }} +{{- end -}} +{{- end -}} + +{{/* +Return the common labels used by selectors. +*/}} +{{- define "virtual-kubelet-saladcloud.matchLabels" -}} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/name: {{ include "virtual-kubelet-saladcloud.name" . }} +{{- end -}} + +{{/* +Return a fully qualified name. +*/}} +{{- define "virtual-kubelet-saladcloud.fullName" -}} +{{- if .Values.fullNameOverride -}} +{{- .Values.fullNameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return a fully qualified `ClusterRole` and `ClusterRoleBinding` name. +*/}} +{{- define "virtual-kubelet-saladcloud.clusterRoleName" -}} +{{- if .Values.clusterRoleNameOverride -}} +{{- .Values.clusterRoleNameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- include "virtual-kubelet-saladcloud.fullName" . -}} +{{- end -}} +{{- end -}} + +{{/* +Return a fully qualified `ServiceAccount` name. +*/}} +{{- define "virtual-kubelet-saladcloud.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{- default (include "virtual-kubelet-saladcloud.fullName" .) .Values.serviceAccountNameOverride -}} +{{- else -}} +{{- default "default" .Values.serviceAccountNameOverride -}} +{{- end -}} +{{- end -}} + +{{/* +Return the most appropriate `apiVersion` for RBAC. +*/}} +{{- define "virtual-kubelet-saladcloud.rbacApiVersion" -}} +{{- if .Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1" -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- end -}} +{{- end -}} diff --git a/charts/virtual-kubelet/templates/clusterrolebinding.yaml b/charts/virtual-kubelet/templates/clusterrolebinding.yaml new file mode 100644 index 0000000..2537fab --- /dev/null +++ b/charts/virtual-kubelet/templates/clusterrolebinding.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create -}} +apiVersion: {{ template "virtual-kubelet-saladcloud.rbacApiVersion" . }} +kind: ClusterRoleBinding +metadata: + {{- with .Values.rbac.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "virtual-kubelet-saladcloud.labels" . | nindent 4 }} + name: {{ include "virtual-kubelet-saladcloud.clusterRoleName" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Values.rbac.clusterRoleName }} +subjects: + - kind: ServiceAccount + name: {{ include "virtual-kubelet-saladcloud.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{ end }} diff --git a/charts/virtual-kubelet/templates/deployment.yaml b/charts/virtual-kubelet/templates/deployment.yaml new file mode 100644 index 0000000..8d86f5e --- /dev/null +++ b/charts/virtual-kubelet/templates/deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + {{- with .Values.provider.deploymentAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "virtual-kubelet-saladcloud.labels" . | nindent 4 }} + name: {{ include "virtual-kubelet-saladcloud.fullName" . }} + namespace: {{ .Release.Namespace }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "virtual-kubelet-saladcloud.matchLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.provider.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "virtual-kubelet-saladcloud.labels" . | nindent 8 }} + spec: + containers: + - name: {{ template "virtual-kubelet-saladcloud.name" . }}-{{ .Values.name }} + {{- if .Values.provider.image.digest }} + image: "{{ .Values.provider.image.repository }}@{{ .Values.provider.image.digest }}" + {{- else }} + image: "{{ .Values.provider.image.repository }}:{{ default .Chart.AppVersion .Values.provider.image.tag }}" + {{- end }} + imagePullPolicy: {{ .Values.provider.image.pullPolicy }} + command: + - "virtual-kubelet" + args: + - "--nodename" + - "{{ required "provider.nodename is required" .Values.provider.nodename }}" + - "--sce-api-key" + - "{{ required "salad.apiKey is required" .Values.salad.apiKey }}" + - "--sce-organization-name" + - "{{ required "salad.organizationName is required" .Values.salad.organizationName }}" + - "--sce-project-name" + - "{{ required "salad.projectName is required" .Values.salad.projectName }}" + {{- if not .Values.provider.taintEnabled }} + - "--disable-taint" + - "true" + {{- end }} + {{- if .Values.provider.logLevel }} + - "--log-level" + - "{{.Values.provider.logLevel}}" + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{- with .Values.provider.nodeSelector }} + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "virtual-kubelet-saladcloud.serviceAccountName" . }} diff --git a/charts/virtual-kubelet/templates/serviceaccount.yaml b/charts/virtual-kubelet/templates/serviceaccount.yaml new file mode 100644 index 0000000..dc5b7db --- /dev/null +++ b/charts/virtual-kubelet/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "virtual-kubelet-saladcloud.labels" . | nindent 4 }} + name: {{ include "virtual-kubelet-saladcloud.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{ end }} diff --git a/charts/virtual-kubelet/values.yaml b/charts/virtual-kubelet/values.yaml new file mode 100644 index 0000000..25de688 --- /dev/null +++ b/charts/virtual-kubelet/values.yaml @@ -0,0 +1,79 @@ +# -- The collection of additional labels applied to all resources. +additionalLabels: {} + +# -- When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.fullName` value in the `ClusterRoleBinding` resource. +clusterRoleNameOverride: "" + +# -- When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.fullName` value. +fullNameOverride: "" + +# -- The list of `Secret` names containing the registry credentials. +imagePullSecrets: [] + +# -- The SaladCloud Virtual Kubelet Provider name. +name: provider + +# -- When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.name` value. +nameOverride: "" + +provider: + # -- The collection of annotations applied to the `Deployment` resource. + deploymentAnnotations: {} + + image: + # -- The SaladCloud Virtual Kubelet Provider image digest. When set to a non-empty value, the image is pulled by digest (ignore the tag value). + digest: "" + + # -- The `imagePullPolicy` for the SaladCloud Virtual Kubelet Provider image. May be `IfNotPresent`, `Always`, or `Never`. + pullPolicy: IfNotPresent + + # -- The SaladCloud Virtual Kubelet Provider image repository URI. + repository: ghcr.io/saladtechnologies/virtual-kubelet-saladcloud + + # -- The SaladCloud Virtual Kubelet Provider image tag. + tag: "" + + # -- The log level. May be `error`, `warn`, `info`, `debug`, or `trace`. + logLevel: warn + + # -- The SaladCloud Virtual Kubelet Provider node name. + nodename: saladcloud-node + + # -- The collection of labels used to assign the SaladCloud Virtual Kubelet Provider pod. + nodeSelector: {} + + # -- The collection of annotations applied to the `Pod` resources created by the deployment. + podAnnotations: {} + + # -- The flag indicating whether the SaladCloud Virtual Kubelet Provider node is tainted. + taintEnabled: true + +rbac: + # -- The flag indicating whether the `ClusterRoleBinding` resource should be created. + create: true + + # -- The collection of annotations applied to the `ClusterRoleBinding` resource. + annotations: {} + + # -- The name of the `ClusterRole` resource. + clusterRoleName: cluster-admin + +salad: + # -- The SaladCloud API key. + apiKey: "" + + # -- The SaladCloud organization name. + organizationName: "" + + # -- The SaladCloud project name. + projectName: "" + +serviceAccount: + # -- The flag indicating whether the `ServiceAccount` resources should be created. + create: true + + # -- The collection of annotations applied to the `ServiceAccount` resource. + annotations: {} + +# -- When set to a non-empty value, overrides the `virtual-kubelet-saladcloud.fullName` value in the `ServiceAccount` resource. +serviceAccountNameOverride: "" diff --git a/cmd/virtual-kubelet/main.go b/cmd/virtual-kubelet/main.go index eedcac2..648ae16 100644 --- a/cmd/virtual-kubelet/main.go +++ b/cmd/virtual-kubelet/main.go @@ -42,13 +42,13 @@ func defaultInputs() models.InputVars { } return models.InputVars{ - NodeName: "saladcloud-edge-provider", + NodeName: "saladcloud-node", KubeConfig: kubeConfig, LogLevel: "info", OrganizationName: "", TaintKey: "virtual-kubelet.io/provider", TaintEffect: "NoSchedule", - TaintValue: "saladCloud", + TaintValue: "saladcloud", ProjectName: "", ApiKey: "", } diff --git a/docker/Dockerfile b/docker/Dockerfile index 55e6034..bdac9ff 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,16 +1,19 @@ # syntax=docker/dockerfile:1 -FROM golang:1.21-alpine AS builder +FROM golang:1.21.3-alpine3.18 AS builder WORKDIR /app COPY go.mod go.sum ./ -RUN go mod download +RUN go mod download && \ + go mod verify COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/bin/virtual-kubelet ./cmd/virtual-kubelet/main.go -FROM scratch +FROM scratch AS final +COPY LICENSE /LICENSE +COPY NOTICE /NOTICE COPY --from=builder --chown=root:root /app/bin/virtual-kubelet /usr/bin/virtual-kubelet ENTRYPOINT ["/usr/bin/virtual-kubelet"] diff --git a/images/salad-logo-500x500.png b/images/salad-logo-500x500.png new file mode 100644 index 0000000..b4df76d Binary files /dev/null and b/images/salad-logo-500x500.png differ