Skip to main content

5 posts tagged with "kubernetes"

View All Tags

· 11 min read
TheBidouilleur

Bonne année, Bonne santé ! Que la réussite et la santé soient avec vous ! J'espère que cette année sera riche en découvertes techniques.

Mais avant d'être trop heureux, attaquons avec une mauvaise nouvelle :

K8S@HOME est mort

Qu'est-ce que K8S@HOME ?

K8S-at-home est le nom d'une communauté promouvant l'usage de Kubernetes comme Homelab. La communauté possédait un énorme dépôt Helm maintenu par quelques membres. Sur ce dépôt Helm, on pouvait avoir la plupart des applications selfhosts utilisées dans les communautés Reddit/Discord. (Plex, Firefly, Bitwarden etc…)

K8S@HOME permettait donc de déployer de nombreuses applications via Helm, sans s'embêter à écrire des charts.

La fin de K8S@HOME

Si toute bonne chose a une fin : voici celle de K8S@HOME. Suite au manque de contributeurs, le dépôt est archivé et les charts ne seront plus maintenus.

Pour l'instant, ça ne veut pas dire que les Helm déjà déployés à partir de K8S@HOME doivent être arrêtés : les images Docker sont choisies dans les fichiers values.yaml, et s'il y a faille → ce seront les images Docker qu'il faudra mettre à jour, pas le chart.

En revanche, avec le temps : nous auront de plus en plus d'instructions obsolètes et nous ne bénéficierons pas des nouvelles fonctionnalités prévues par Helm.

La Solution : Créer notre dépôt Helm

Mais oui ! Tout comme nous prenons l'habitude de créer un registre Docker avec nos images. Nous pouvons créer un dépôt Helm avec nos charts.

Fonctionnement d'un chart Helm

De base, un chart Helm se compose de différents fichiers YAML qui seront appliqués via kubectl après un traitement de "templating". Ce traitement permet de remplacer des valeurs dans les fichiers (Les utilisateurs de Jinja2 ne seront pas perdus) via le fichier values.yaml (qui contient les valeurs de remplacement) et les fichiers _helpers.tpl (qui contiennent des fonctions pour traiter les valeurs).

Une fois que les valeurs de remplacements sont appliquées sur la template, on envoie les modifications au cluster. (via kubectl ou en communiquant avec l'API).

Exemple rapide :

#service.yml
apiVersion: v1
kind: Service
metadata:
name: baikal
labels:
{{- include "baikal.labels" . | nindent 4 }}
spec:
type: ClusterIP
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "baikal.selectorLabels" . | nindent 4 }}
#values.yml
service:
type: ClusterIP
port: 80

Les fonctions sont appelées par le mot clé "include", et les valeurs du fichier values.yml sont appelées via le préfixe ".Values".

Vous pourrez apprendre à faire des charts Helm en suivant l'excellente documentation de Stéphane Robert : ici

Fonctionnement d'un dépôt Helm

Lorsque l'on ajoute un dépôt, on peut directement voir les charts disponibles dans ce dépôt :

helm repo add qjoly https://qjoly.github.io/helm-charts\
helm search repo qjoly

C'est grâce au dépôt qui contient un fichier index.yaml qui va répertorier les charts disponibles et les URLs permettant de les télécharger.

Schéma résumant le fonctionnement d'un depot

Ainsi, lorsque l'on veut ajouter le chart "Joplin" dépôt "qjoly", notre client va aller chercher dans notre fichier index.yaml l'url de téléchargement (en tar.gz) du chart. Notre client Helm va ensuite faire le remplacement des valeurs avant d'envoyer le manifest dans notre cluster.

Création d'un dépôt Helm

Pour créer un dépôt helm, voici les différentes solutions :

  • Utiliser ChartMuseum
  • Utiliser l'image GitHub Action Chart-Releaser
  • à la main (en créant le fichier index.yaml manuellement)

Nous, nous passerons par l'image GitHub Action (je réserve une page sur ChartMuseum).

Usage de GitHub Action pour générer les releases

       - name: Run chart-releaser
uses: helm/chart-releaser-action@v1.5.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
Ne pas créer de secret

Il n'est pas nécessaire de créer un secret. (celui-ci est automatiquement généré durant le CI)

Grâce à cette image, CR va chercher dans le dossier charts/ pour générer l'index.yaml et le stocker directement dans la branche gh-pages. (pensez à activer GitHub-pages pour que le site soit accessible à https://votre-username.github.io/votre-projet)

L'avantage de Chart-Releaser est qu'il va automatiquement créer des releases pour chaque chart présent dans notre dépôt GitHub. Ce sont d'ailleurs ces mêmes releases qui seront accessibles depuis le fichier index.yaml. En revanche, CR ne pourra pas mettre à jour une version déjà existante. Si nous voulons refaire la version 1.5 du chart "Baikal", il n'en fera rien. Il faudra manuellement supprimer la release/tag avant de relancer le CI.

curl https://qjoly.github.io/helm-charts/index.yaml

Nous avons bien un fichier renvoyant les charts disponibles, une description, ainsi que l'URL où l'archive du chart est accessible.

Il est très bien possible de se contenter de ça, mais puisque nous le pouvons : allons plus-loin !

Tester les charts avant de générer la release. (CI)

Pour être sûr de ne pas envoyer des charts non fonctionnels, j'ai voulu m'appuyer sur du CI pour vérifier le bon-fonctionnement de mon code.

La première chose simple que nous pouvons faire... c'est d'utiliser le linter de Helm.

Vérification de la syntaxe

Selon Wikipedia:

Un linter est un outil qui analyse le code source pour signaler les erreurs de programmation, les bogues, les erreurs stylistiques et les constructions suspectes.

L'objectif est donc de vérifier (avant d'exécuter un code) que sa syntaxe est correcte et qu'il n'y a pas d'erreur évidente. Nous pouvons directement taper la commande helm lint ..

Exemple :

➜  baikal git:(main) helm lint .
==> Linting .
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed
➜ baikal git:(main)

Helm lint est CI-Friendly, il renvoie un exit-code différent de 0 lorsque le lintage (c'est mon article, j'invente les mots que je veux) n'est pas correct.

Pour tester l'intégralité de mes charts, j'ai écrit un petit script helm_lint.sh qui va effectuer la commande helm lint . dans chaque sous-dossier de charts/.

cd ../../charts
for d in *
do
echo "Testing $d "
(cd "$d" && helm lint )
if [ $? -ne 0 ]; then
echo "Error"
exit 1
fi
done

Ainsi, à la moindre erreur dans le script (si le lintage est mauvais), celui-ci s'arrête et renvoie l'exitcode à 1. (Ce qui va stopper le CI et générer une erreur)


Pour lancer ce script via GitHub Action, j'ai installé Helm via l'action "azure/setup-helm". Ce qui nous donne ces instructions à rajouter devant notre chart-releaser :

       - uses: azure/setup-helm@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Helm Lint
run: |
cd .github/workflows/
./helm_lint.sh

Vérification du fonctionnement des charts

Si vérifier le lint se fait en quelques secondes, il est également possible de lancer le chart directement depuis le CI Github.

Pour cela, il faut au préalablement créer un cluster Kubernetes depuis Github Action. Et si cela est possible, c'est grâce à KIND (Kubernetes INside Docker) qui permet de faire un cluster virtuel dans des conteneurs Docker en seulement quelques commandes.

À rajouter dans notre CI:

       - name: Create k8s Kind Cluster
uses: helm/kind-action@v1.5.0

Maintenant que nos charts ont une syntaxe correcte, que nous avons un cluster fonctionnel, il faut y installer nos programmes un-par-un et les tester individuellement.

Vous rappelez-vous de helm_lint.sh ? Voici son grand-frère : helm_deploy.sh.

cd ../../charts
for d in *
do
echo "Deploying $d to kind"
(
set -x
cd "$d"
if [ -f ".no_ci" ]; then
echo "No CI for this chart."
else
helm install $d . --wait --timeout 120s
helm test $d
fi
)
if [ $? -ne 0 ]; then
echo "Error during deployment"
exit 1
else
echo "Success ! "
helm delete $d || true
fi
done

L'exécution de ce script va déployer chaque chart individuellement en lançant la commande helm test, permettant de lancer des tests (vérifier un port, vérifier le status d'une page web etc..). Si le test échoue, helm test renverra un exitcode à 1, et le script créera une erreur.

J'ai également la vérification de la présence d'un fichier .no_ci qui, comme son nom l'indique, permet de "skip" un chart. Cela permet de ne pas déployer certains charts dans le cluster de test. (Par exemple : mon chart plex-nfs qui ne peut pas fonctionner dans Github Action, ou un chart OpenLDAP).

Ressources ?

GitHub Action est limitée à 2000 minutes de CI mensuels. Avec une petite dizaine de charts, mes tests durent environ 5min. (soit 400 tests par mois)

Je suis conscient qu'à notre échelle : c'est suffisant. Mais à garder en tête si on commence à avoir un dépôt similaire à k8s-at-home.

Un README dynamique

Et pour rendre votre dépôt GitHub agréable pour vos utilisateurs, nous pouvons faire un README évoluant au fur et à mesure que vous créez vos charts.

L'idée est donc de créer un tableau comme celui-ci :

NameDescriptionChart VersionApp Version
baikalBaïkal is a lightweight CalDAV+CardDAV server0.1.60.9.2

Ces informations (nom, description, version) sont toutes accessibles depuis les fichiers Chart.yaml présents dans nos charts.

apiVersion: v2
name: baikal
description: Baïkal is a lightweight CalDAV+CardDAV server
type: application
version: 0.1.6
appVersion: "0.9.2"
keywords:
- baikal
home: https://sabre.io/baikal/
maintainers:
- email: github@thoughtless.eu
name: QJOLY
url: https://thebidouilleur.xyz
sources:
- https://github.com/sabre-io/Baikal
- https://github.com/QJoly/helm-charts

Du coup, vous rappelez-vous des scripts helm_lint.sh et helm_deploy.sh ? Eh bien voici le tonton : get_readme.py.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, logging, os, yaml
from pathlib import Path
from glob import glob
from yaml.loader import SafeLoader
from jinja2 import Template

def main():
files = glob('../../**/Chart.yaml', recursive=True)
charts = []
for chart in files:
with open(chart) as f:
data = yaml.load(f, Loader=SafeLoader)
print(f"nom : {data['name']} \ndescription: {data['description']}\nversion chart: {data['version']}\nversion app: {data['appVersion']}")
charts.append([data['name'],data['description'],data['version'], data['appVersion']])
print(f"Nombre de charts: {len(charts)}")
table_template=Path('table.j2').read_text()
tm = Template(table_template)
tableValue = tm.render({'charts':charts})
print("----")
readme_template=Path('./README.md.tmpl').read_text().replace("CHARTS_TABLE",tableValue).replace(""", '"')
print(readme_template)
Path("../../README.md").write_text(readme_template)

if __name__ == "__main__":
main()

Ce script Python va récupérer les différentes balises contenues dans les Charts.yaml, puis va générer un tableau Markdown à partir du fichier table.j2 (en jinja2, tout comme Helm), et va créer un README à partir du tableau ainsi que du fichier README.md.tmpl (contenant de la mise en page, et des informations supplémentaires).

Voici le résultat actuel :

Visuel actuel

à ajouter sur notre CI :

       - name: Modifying the readme on main
continue-on-error: true
run: |
git pull
git checkout main
cd .github/workflows/
python -m pip install -r requirements.txt
python3 get_readme.py
cd ../..
git add README.md
git commit -m ":lock: Auto-Update README with Charts versions"
git push

Créer une page d'accueil

Dans cet état, lorsque nous donnons l'URL du dépôt à ajouter aux clients helm de nos utilisateurs : ceux-ci accèderont sur une 404 (logique, le seul fichier créé est l'index.yaml). Mon idée est de reprendre les mêmes informations du README pour l'afficher sous forme de page web.

Si à la base, je voulais créer un système similaire au README (mais en HTML), j'ai opté pour la conversion du Markdown en HTML. Et un outil très utilisé est : pandoc

Nous allons donc utiliser Pandoc pour convertir notre readme en HTML, l'instruction est simple :

       - name: Setup Pandoc
uses: nikeee/setup-pandoc@v1

- name: Modifying index.html
continue-on-error: true
run: |
index=$(pandoc --from markdown_github README.md --to html5 --standalone --toc --citeproc --no-highlight)
git checkout gh-pages
echo $index > index.html
git add index.html
git commit -m "[AUTO] Update index.html of gh-pages"
git push

On ne peut pas dire que du grand art, mais le résultat est plutôt propre (dieu merci, Pandoc intègre du CSS).

Rendu du site

Dans mon cas, je rajoute même mon script JS de compteur de vues à cette étape.

Conclusion

Créer un dépôt Helm avec les petites modifications que je vous propose ne fera pas de vous un grand développeur (?) de charts. Mais ces outils vous permettrons de vous faciliter le travail, et de proposer une expérience agréable pour les personnes utilisant vos codes.

Il est toujours possible d'aller de plus en plus loin. Je pense notamment à RenovateBot qui peut vous proposer des modifications (ex: mettre à jour une image par défaut).

N'hésitez pas à me faire parvenir vos retours (mail/Twitter) ou vos propositions d'améliorations.

PS: Pour obtenir le CI complet (en reprenant chaque étape de cette page), vous pouvez visionner mon dépot ici.

· 42 min read
TheBidouilleur

Terraform is often referred to as the "remote control" of DevOps, and it has some pretty big numbers: 2626 providers and 11397 modules.

A provider?

A module is an integration of Terraform with an external tool. You can launch a playbook, create an instance on AWS, or even send a message on Slack.

I actively use Terraform in my deployments (creating and/or populating a VM under Proxmox/LibVirt), but since my infrastructure is based on Kubernetes, I wonder about the place that Terraform in my deployments. (Excluding the first cluster installation)

So I've been looking at Terraform and Kubernetes together.

The advantages of Kubernetes and Terraform?

Kubernetes works fine without Terraform, why start adding tools to the equation?

Kubernetes suffers from a great evil: YAML. And even if I love YAML (Really, I don't want to go back to JSON...): it remains a simple format and not a real programming language.

That's why HCL can potentially open doors for us by offering integrations to other providers.

A small example as an appetizer

A YAML configmap

If I ever want to create a configmap containing a YAML for an application. Here is the file I want to store:

twitter=thebidouilleur
jobs=developper
favorite.meal=rougail
vehicule=electricunicycle

We can create our YAML file with the right header, and indent the content of our file so that YAML recognizes it as a text block.

apiVersion: v1
kind: ConfigMap
metadata:
name: data-user
namespace: hcl
data:
data.ini: |
twitter=thebidouilleur
jobs=developper
favorite.meal=rougail
vehicule=electricunicycle

Easy, isn't it?

And now we try the same thing with this file?
{
"pokemon": [{
"id": 1,
"num": "001",
"name": "Bulbasaur",
"img": "http://www.serebii.net/pokemongo/pokemon/001.png",
"type": [
"Grass",
"Poison"
],
"height": "0.71 m",
"weight": "6.9 kg",
"candy": "Bulbasaur Candy",
"candy_count": 25,
"egg": "2 km",
"spawn_chance": 0.69,
"avg_spawns": 69,
"spawn_time": "20:00",
"multipliers": [1.58],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"next_evolution": [{
"num": "002",
"name": "Ivysaur"
}, {
"num": "003",
"name": "Venusaur"
}]
}, {
"id": 2,
"num": "002",
"name": "Ivysaur",
"img": "http://www.serebii.net/pokemongo/pokemon/002.png",
"type": [
"Grass",
"Poison"
],
"height": "0.99 m",
"weight": "13.0 kg",
"candy": "Bulbasaur Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.042,
"avg_spawns": 4.2,
"spawn_time": "07:00",
"multipliers": [
1.2,
1.6
],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"prev_evolution": [{
"num": "001",
"name": "Bulbasaur"
}],
"next_evolution": [{
"num": "003",
"name": "Venusaur"
}]
}, {
"id": 3,
"num": "003",
"name": "Venusaur",
"img": "http://www.serebii.net/pokemongo/pokemon/003.png",
"type": [
"Grass",
"Poison"
],
"height": "2.01 m",
"weight": "100.0 kg",
"candy": "Bulbasaur Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.017,
"avg_spawns": 1.7,
"spawn_time": "11:30",
"multipliers": null,
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"prev_evolution": [{
"num": "001",
"name": "Bulbasaur"
}, {
"num": "002",
"name": "Ivysaur"
}]
}, {
"id": 4,
"num": "004",
"name": "Charmander",
"img": "http://www.serebii.net/pokemongo/pokemon/004.png",
"type": [
"Fire"
],
"height": "0.61 m",
"weight": "8.5 kg",
"candy": "Charmander Candy",
"candy_count": 25,
"egg": "2 km",
"spawn_chance": 0.253,
"avg_spawns": 25.3,
"spawn_time": "08:45",
"multipliers": [1.65],
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"next_evolution": [{
"num": "005",
"name": "Charmeleon"
}, {
"num": "006",
"name": "Charizard"
}]
}, {
"id": 5,
"num": "005",
"name": "Charmeleon",
"img": "http://www.serebii.net/pokemongo/pokemon/005.png",
"type": [
"Fire"
],
"height": "1.09 m",
"weight": "19.0 kg",
"candy": "Charmander Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.012,
"avg_spawns": 1.2,
"spawn_time": "19:00",
"multipliers": [1.79],
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"prev_evolution": [{
"num": "004",
"name": "Charmander"
}],
"next_evolution": [{
"num": "006",
"name": "Charizard"
}]
}, {
"id": 6,
"num": "006",
"name": "Charizard",
"img": "http://www.serebii.net/pokemongo/pokemon/006.png",
"type": [
"Fire",
"Flying"
],
"height": "1.70 m",
"weight": "90.5 kg",
"candy": "Charmander Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0031,
"avg_spawns": 0.31,
"spawn_time": "13:34",
"multipliers": null,
"weaknesses": [
"Water",
"Electric",
"Rock"
],
"prev_evolution": [{
"num": "004",
"name": "Charmander"
}, {
"num": "005",
"name": "Charmeleon"
}]
}, {
"id": 7,
"num": "007",
"name": "Squirtle",
"img": "http://www.serebii.net/pokemongo/pokemon/007.png",
"type": [
"Water"
],
"height": "0.51 m",
"weight": "9.0 kg",
"candy": "Squirtle Candy",
"candy_count": 25,
"egg": "2 km",
"spawn_chance": 0.58,
"avg_spawns": 58,
"spawn_time": "04:25",
"multipliers": [2.1],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "008",
"name": "Wartortle"
}, {
"num": "009",
"name": "Blastoise"
}]
}, {
"id": 8,
"num": "008",
"name": "Wartortle",
"img": "http://www.serebii.net/pokemongo/pokemon/008.png",
"type": [
"Water"
],
"height": "0.99 m",
"weight": "22.5 kg",
"candy": "Squirtle Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.034,
"avg_spawns": 3.4,
"spawn_time": "07:02",
"multipliers": [1.4],
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "007",
"name": "Squirtle"
}],
"next_evolution": [{
"num": "009",
"name": "Blastoise"
}]
}, {
"id": 9,
"num": "009",
"name": "Blastoise",
"img": "http://www.serebii.net/pokemongo/pokemon/009.png",
"type": [
"Water"
],
"height": "1.60 m",
"weight": "85.5 kg",
"candy": "Squirtle Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0067,
"avg_spawns": 0.67,
"spawn_time": "00:06",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "007",
"name": "Squirtle"
}, {
"num": "008",
"name": "Wartortle"
}]
}, {
"id": 10,
"num": "010",
"name": "Caterpie",
"img": "http://www.serebii.net/pokemongo/pokemon/010.png",
"type": [
"Bug"
],
"height": "0.30 m",
"weight": "2.9 kg",
"candy": "Caterpie Candy",
"candy_count": 12,
"egg": "2 km",
"spawn_chance": 3.032,
"avg_spawns": 303.2,
"spawn_time": "16:35",
"multipliers": [1.05],
"weaknesses": [
"Fire",
"Flying",
"Rock"
],
"next_evolution": [{
"num": "011",
"name": "Metapod"
}, {
"num": "012",
"name": "Butterfree"
}]
}, {
"id": 11,
"num": "011",
"name": "Metapod",
"img": "http://www.serebii.net/pokemongo/pokemon/011.png",
"type": [
"Bug"
],
"height": "0.71 m",
"weight": "9.9 kg",
"candy": "Caterpie Candy",
"candy_count": 50,
"egg": "Not in Eggs",
"spawn_chance": 0.187,
"avg_spawns": 18.7,
"spawn_time": "02:11",
"multipliers": [
3.55,
3.79
],
"weaknesses": [
"Fire",
"Flying",
"Rock"
],
"prev_evolution": [{
"num": "010",
"name": "Caterpie"
}],
"next_evolution": [{
"num": "012",
"name": "Butterfree"
}]
}, {
"id": 12,
"num": "012",
"name": "Butterfree",
"img": "http://www.serebii.net/pokemongo/pokemon/012.png",
"type": [
"Bug",
"Flying"
],
"height": "1.09 m",
"weight": "32.0 kg",
"candy": "Caterpie Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.022,
"avg_spawns": 2.2,
"spawn_time": "05:23",
"multipliers": null,
"weaknesses": [
"Fire",
"Electric",
"Ice",
"Flying",
"Rock"
],
"prev_evolution": [{
"num": "010",
"name": "Caterpie"
}, {
"num": "011",
"name": "Metapod"
}]
}, {
"id": 13,
"num": "013",
"name": "Weedle",
"img": "http://www.serebii.net/pokemongo/pokemon/013.png",
"type": [
"Bug",
"Poison"
],
"height": "0.30 m",
"weight": "3.2 kg",
"candy": "Weedle Candy",
"candy_count": 12,
"egg": "2 km",
"spawn_chance": 7.12,
"avg_spawns": 712,
"spawn_time": "02:21",
"multipliers": [
1.01,
1.09
],
"weaknesses": [
"Fire",
"Flying",
"Psychic",
"Rock"
],
"next_evolution": [{
"num": "014",
"name": "Kakuna"
}, {
"num": "015",
"name": "Beedrill"
}]
}, {
"id": 14,
"num": "014",
"name": "Kakuna",
"img": "http://www.serebii.net/pokemongo/pokemon/014.png",
"type": [
"Bug",
"Poison"
],
"height": "0.61 m",
"weight": "10.0 kg",
"candy": "Weedle Candy",
"candy_count": 50,
"egg": "Not in Eggs",
"spawn_chance": 0.44,
"avg_spawns": 44,
"spawn_time": "02:30",
"multipliers": [
3.01,
3.41
],
"weaknesses": [
"Fire",
"Flying",
"Psychic",
"Rock"
],
"prev_evolution": [{
"num": "013",
"name": "Weedle"
}],
"next_evolution": [{
"num": "015",
"name": "Beedrill"
}]
}, {
"id": 15,
"num": "015",
"name": "Beedrill",
"img": "http://www.serebii.net/pokemongo/pokemon/015.png",
"type": [
"Bug",
"Poison"
],
"height": "0.99 m",
"weight": "29.5 kg",
"candy": "Weedle Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.051,
"avg_spawns": 5.1,
"spawn_time": "04:50",
"multipliers": null,
"weaknesses": [
"Fire",
"Flying",
"Psychic",
"Rock"
],
"prev_evolution": [{
"num": "013",
"name": "Weedle"
}, {
"num": "014",
"name": "Kakuna"
}]
}, {
"id": 16,
"num": "016",
"name": "Pidgey",
"img": "http://www.serebii.net/pokemongo/pokemon/016.png",
"type": [
"Normal",
"Flying"
],
"height": "0.30 m",
"weight": "1.8 kg",
"candy": "Pidgey Candy",
"candy_count": 12,
"egg": "2 km",
"spawn_chance": 15.98,
"avg_spawns": 1.598,
"spawn_time": "01:34",
"multipliers": [
1.71,
1.92
],
"weaknesses": [
"Electric",
"Rock"
],
"next_evolution": [{
"num": "017",
"name": "Pidgeotto"
}, {
"num": "018",
"name": "Pidgeot"
}]
}, {
"id": 17,
"num": "017",
"name": "Pidgeotto",
"img": "http://www.serebii.net/pokemongo/pokemon/017.png",
"type": [
"Normal",
"Flying"
],
"height": "1.09 m",
"weight": "30.0 kg",
"candy": "Pidgey Candy",
"candy_count": 50,
"egg": "Not in Eggs",
"spawn_chance": 1.02,
"avg_spawns": 102,
"spawn_time": "01:30",
"multipliers": [1.79],
"weaknesses": [
"Electric",
"Rock"
],
"prev_evolution": [{
"num": "016",
"name": "Pidgey"
}],
"next_evolution": [{
"num": "018",
"name": "Pidgeot"
}]
}, {
"id": 18,
"num": "018",
"name": "Pidgeot",
"img": "http://www.serebii.net/pokemongo/pokemon/018.png",
"type": [
"Normal",
"Flying"
],
"height": "1.50 m",
"weight": "39.5 kg",
"candy": "Pidgey Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.13,
"avg_spawns": 13,
"spawn_time": "01:50",
"multipliers": null,
"weaknesses": [
"Electric",
"Rock"
],
"prev_evolution": [{
"num": "016",
"name": "Pidgey"
}, {
"num": "017",
"name": "Pidgeotto"
}]
}, {
"id": 19,
"num": "019",
"name": "Rattata",
"img": "http://www.serebii.net/pokemongo/pokemon/019.png",
"type": [
"Normal"
],
"height": "0.30 m",
"weight": "3.5 kg",
"candy": "Rattata Candy",
"candy_count": 25,
"egg": "2 km",
"spawn_chance": 13.05,
"avg_spawns": 1.305,
"spawn_time": "01:55",
"multipliers": [
2.55,
2.73
],
"weaknesses": [
"Fighting"
],
"next_evolution": [{
"num": "020",
"name": "Raticate"
}]
}, {
"id": 20,
"num": "020",
"name": "Raticate",
"img": "http://www.serebii.net/pokemongo/pokemon/020.png",
"type": [
"Normal"
],
"height": "0.71 m",
"weight": "18.5 kg",
"candy": "Rattata Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.41,
"avg_spawns": 41,
"spawn_time": "01:56",
"multipliers": null,
"weaknesses": [
"Fighting"
],
"prev_evolution": [{
"num": "019",
"name": "Rattata"
}]
}, {
"id": 21,
"num": "021",
"name": "Spearow",
"img": "http://www.serebii.net/pokemongo/pokemon/021.png",
"type": [
"Normal",
"Flying"
],
"height": "0.30 m",
"weight": "2.0 kg",
"candy": "Spearow Candy",
"candy_count": 50,
"egg": "2 km",
"spawn_chance": 4.73,
"avg_spawns": 473,
"spawn_time": "12:25",
"multipliers": [
2.66,
2.68
],
"weaknesses": [
"Electric",
"Rock"
],
"next_evolution": [{
"num": "022",
"name": "Fearow"
}]
}, {
"id": 22,
"num": "022",
"name": "Fearow",
"img": "http://www.serebii.net/pokemongo/pokemon/022.png",
"type": [
"Normal",
"Flying"
],
"height": "1.19 m",
"weight": "38.0 kg",
"candy": "Spearow Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.15,
"avg_spawns": 15,
"spawn_time": "01:11",
"multipliers": null,
"weaknesses": [
"Electric",
"Rock"
],
"prev_evolution": [{
"num": "021",
"name": "Spearow"
}]
}, {
"id": 23,
"num": "023",
"name": "Ekans",
"img": "http://www.serebii.net/pokemongo/pokemon/023.png",
"type": [
"Poison"
],
"height": "2.01 m",
"weight": "6.9 kg",
"candy": "Ekans Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 2.27,
"avg_spawns": 227,
"spawn_time": "12:20",
"multipliers": [
2.21,
2.27
],
"weaknesses": [
"Ground",
"Psychic"
],
"next_evolution": [{
"num": "024",
"name": "Arbok"
}]
}, {
"id": 24,
"num": "024",
"name": "Arbok",
"img": "http://www.serebii.net/pokemongo/pokemon/024.png",
"type": [
"Poison"
],
"height": "3.51 m",
"weight": "65.0 kg",
"candy": "Ekans Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.072,
"avg_spawns": 7.2,
"spawn_time": "01:50",
"multipliers": null,
"weaknesses": [
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "023",
"name": "Ekans"
}]
}, {
"id": 25,
"num": "025",
"name": "Pikachu",
"img": "http://www.serebii.net/pokemongo/pokemon/025.png",
"type": [
"Electric"
],
"height": "0.41 m",
"weight": "6.0 kg",
"candy": "Pikachu Candy",
"candy_count": 50,
"egg": "2 km",
"spawn_chance": 0.21,
"avg_spawns": 21,
"spawn_time": "04:00",
"multipliers": [2.34],
"weaknesses": [
"Ground"
],
"next_evolution": [{
"num": "026",
"name": "Raichu"
}]
}, {
"id": 26,
"num": "026",
"name": "Raichu",
"img": "http://www.serebii.net/pokemongo/pokemon/026.png",
"type": [
"Electric"
],
"height": "0.79 m",
"weight": "30.0 kg",
"candy": "Pikachu Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0076,
"avg_spawns": 0.76,
"spawn_time": "23:58",
"multipliers": null,
"weaknesses": [
"Ground"
],
"prev_evolution": [{
"num": "025",
"name": "Pikachu"
}]
}, {
"id": 27,
"num": "027",
"name": "Sandshrew",
"img": "http://www.serebii.net/pokemongo/pokemon/027.png",
"type": [
"Ground"
],
"height": "0.61 m",
"weight": "12.0 kg",
"candy": "Sandshrew Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 1.11,
"avg_spawns": 111,
"spawn_time": "01:58",
"multipliers": [2.45],
"weaknesses": [
"Water",
"Grass",
"Ice"
],
"next_evolution": [{
"num": "028",
"name": "Sandslash"
}]
}, {
"id": 28,
"num": "028",
"name": "Sandslash",
"img": "http://www.serebii.net/pokemongo/pokemon/028.png",
"type": [
"Ground"
],
"height": "0.99 m",
"weight": "29.5 kg",
"candy": "Sandshrew Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.037,
"avg_spawns": 3.7,
"spawn_time": "12:34",
"multipliers": null,
"weaknesses": [
"Water",
"Grass",
"Ice"
],
"prev_evolution": [{
"num": "027",
"name": "Sandshrew"
}]
}, {
"id": 29,
"num": "029",
"name": "Nidoran ♀ (Female)",
"img": "http://www.serebii.net/pokemongo/pokemon/029.png",
"type": [
"Poison"
],
"height": "0.41 m",
"weight": "7.0 kg",
"candy": "Nidoran ♀ (Female) Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 1.38,
"avg_spawns": 138,
"spawn_time": "01:51",
"multipliers": [
1.63,
2.48
],
"weaknesses": [
"Ground",
"Psychic"
],
"next_evolution": [{
"num": "030",
"name": "Nidorina"
}, {
"num": "031",
"name": "Nidoqueen"
}]
}, {
"id": 30,
"num": "030",
"name": "Nidorina",
"img": "http://www.serebii.net/pokemongo/pokemon/030.png",
"type": [
"Poison"
],
"height": "0.79 m",
"weight": "20.0 kg",
"candy": "Nidoran ♀ (Female) Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.088,
"avg_spawns": 8.8,
"spawn_time": "07:22",
"multipliers": [
1.83,
2.48
],
"weaknesses": [
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "029",
"name": "Nidoran(Female)"
}],
"next_evolution": [{
"num": "031",
"name": "Nidoqueen"
}]
}, {
"id": 31,
"num": "031",
"name": "Nidoqueen",
"img": "http://www.serebii.net/pokemongo/pokemon/031.png",
"type": [
"Poison",
"Ground"
],
"height": "1.30 m",
"weight": "60.0 kg",
"candy": "Nidoran ♀ (Female) Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.012,
"avg_spawns": 1.2,
"spawn_time": "12:35",
"multipliers": null,
"weaknesses": [
"Water",
"Ice",
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "029",
"name": "Nidoran(Female)"
}, {
"num": "030",
"name": "Nidorina"
}]
}, {
"id": 32,
"num": "032",
"name": "Nidoran ♂ (Male)",
"img": "http://www.serebii.net/pokemongo/pokemon/032.png",
"type": [
"Poison"
],
"height": "0.51 m",
"weight": "9.0 kg",
"candy": "Nidoran ♂ (Male) Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 1.31,
"avg_spawns": 131,
"spawn_time": "01:12",
"multipliers": [
1.64,
1.7
],
"weaknesses": [
"Ground",
"Psychic"
],
"next_evolution": [{
"num": "033",
"name": "Nidorino"
}, {
"num": "034",
"name": "Nidoking"
}]
}, {
"id": 33,
"num": "033",
"name": "Nidorino",
"img": "http://www.serebii.net/pokemongo/pokemon/033.png",
"type": [
"Poison"
],
"height": "0.89 m",
"weight": "19.5 kg",
"candy": "Nidoran ♂ (Male) Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.083,
"avg_spawns": 8.3,
"spawn_time": "09:02",
"multipliers": [1.83],
"weaknesses": [
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "032",
"name": "Nidoran(Male)"
}],
"next_evolution": [{
"num": "034",
"name": "Nidoking"
}]
}, {
"id": 34,
"num": "034",
"name": "Nidoking",
"img": "http://www.serebii.net/pokemongo/pokemon/034.png",
"type": [
"Poison",
"Ground"
],
"height": "1.40 m",
"weight": "62.0 kg",
"candy": "Nidoran ♂ (Male) Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.017,
"avg_spawns": 1.7,
"spawn_time": "12:16",
"multipliers": null,
"weaknesses": [
"Water",
"Ice",
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "032",
"name": "Nidoran(Male)"
}, {
"num": "033",
"name": "Nidorino"
}]
}, {
"id": 35,
"num": "035",
"name": "Clefairy",
"img": "http://www.serebii.net/pokemongo/pokemon/035.png",
"type": [
"Normal"
],
"height": "0.61 m",
"weight": "7.5 kg",
"candy": "Clefairy Candy",
"candy_count": 50,
"egg": "2 km",
"spawn_chance": 0.92,
"avg_spawns": 92,
"spawn_time": "03:30",
"multipliers": [
2.03,
2.14
],
"weaknesses": [
"Fighting"
],
"next_evolution": [{
"num": "036",
"name": "Clefable"
}]
}, {
"id": 36,
"num": "036",
"name": "Clefable",
"img": "http://www.serebii.net/pokemongo/pokemon/036.png",
"type": [
"Normal"
],
"height": "1.30 m",
"weight": "40.0 kg",
"candy": "Clefairy Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.012,
"avg_spawns": 1.2,
"spawn_time": "03:29",
"multipliers": null,
"weaknesses": [
"Fighting"
],
"prev_evolution": [{
"num": "035",
"name": "Clefairy"
}]
}, {
"id": 37,
"num": "037",
"name": "Vulpix",
"img": "http://www.serebii.net/pokemongo/pokemon/037.png",
"type": [
"Fire"
],
"height": "0.61 m",
"weight": "9.9 kg",
"candy": "Vulpix Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.22,
"avg_spawns": 22,
"spawn_time": "13:43",
"multipliers": [
2.74,
2.81
],
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"next_evolution": [{
"num": "038",
"name": "Ninetales"
}]
}, {
"id": 38,
"num": "038",
"name": "Ninetales",
"img": "http://www.serebii.net/pokemongo/pokemon/038.png",
"type": [
"Fire"
],
"height": "1.09 m",
"weight": "19.9 kg",
"candy": "Vulpix Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0077,
"avg_spawns": 0.77,
"spawn_time": "01:32",
"multipliers": null,
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"prev_evolution": [{
"num": "037",
"name": "Vulpix"
}]
}, {
"id": 39,
"num": "039",
"name": "Jigglypuff",
"img": "http://www.serebii.net/pokemongo/pokemon/039.png",
"type": [
"Normal"
],
"height": "0.51 m",
"weight": "5.5 kg",
"candy": "Jigglypuff Candy",
"candy_count": 50,
"egg": "2 km",
"spawn_chance": 0.39,
"avg_spawns": 39,
"spawn_time": "08:46",
"multipliers": [1.85],
"weaknesses": [
"Fighting"
],
"next_evolution": [{
"num": "040",
"name": "Wigglytuff"
}]
}, {
"id": 40,
"num": "040",
"name": "Wigglytuff",
"img": "http://www.serebii.net/pokemongo/pokemon/040.png",
"type": [
"Normal"
],
"height": "0.99 m",
"weight": "12.0 kg",
"candy": "Jigglypuff Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.018,
"avg_spawns": 1.8,
"spawn_time": "12:28",
"multipliers": null,
"weaknesses": [
"Fighting"
],
"prev_evolution": [{
"num": "039",
"name": "Jigglypuff"
}]
}, {
"id": 41,
"num": "041",
"name": "Zubat",
"img": "http://www.serebii.net/pokemongo/pokemon/041.png",
"type": [
"Poison",
"Flying"
],
"height": "0.79 m",
"weight": "7.5 kg",
"candy": "Zubat Candy",
"candy_count": 50,
"egg": "2 km",
"spawn_chance": 6.52,
"avg_spawns": 652,
"spawn_time": "12:28",
"multipliers": [
2.6,
3.67
],
"weaknesses": [
"Electric",
"Ice",
"Psychic",
"Rock"
],
"next_evolution": [{
"num": "042",
"name": "Golbat"
}]
}, {
"id": 42,
"num": "042",
"name": "Golbat",
"img": "http://www.serebii.net/pokemongo/pokemon/042.png",
"type": [
"Poison",
"Flying"
],
"height": "1.60 m",
"weight": "55.0 kg",
"candy": "Zubat Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.42,
"avg_spawns": 42,
"spawn_time": "02:15",
"multipliers": null,
"weaknesses": [
"Electric",
"Ice",
"Psychic",
"Rock"
],
"prev_evolution": [{
"num": "041",
"name": "Zubat"
}]
}, {
"id": 43,
"num": "043",
"name": "Oddish",
"img": "http://www.serebii.net/pokemongo/pokemon/043.png",
"type": [
"Grass",
"Poison"
],
"height": "0.51 m",
"weight": "5.4 kg",
"candy": "Oddish Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 1.02,
"avg_spawns": 102,
"spawn_time": "03:58",
"multipliers": [1.5],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"next_evolution": [{
"num": "044",
"name": "Gloom"
}, {
"num": "045",
"name": "Vileplume"
}]
}, {
"id": 44,
"num": "044",
"name": "Gloom",
"img": "http://www.serebii.net/pokemongo/pokemon/044.png",
"type": [
"Grass",
"Poison"
],
"height": "0.79 m",
"weight": "8.6 kg",
"candy": "Oddish Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.064,
"avg_spawns": 6.4,
"spawn_time": "11:33",
"multipliers": [1.49],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"prev_evolution": [{
"num": "043",
"name": "Oddish"
}],
"next_evolution": [{
"num": "045",
"name": "Vileplume"
}]
}, {
"id": 45,
"num": "045",
"name": "Vileplume",
"img": "http://www.serebii.net/pokemongo/pokemon/045.png",
"type": [
"Grass",
"Poison"
],
"height": "1.19 m",
"weight": "18.6 kg",
"candy": "Oddish Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0097,
"avg_spawns": 0.97,
"spawn_time": "23:58",
"multipliers": null,
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"prev_evolution": [{
"num": "043",
"name": "Oddish"
}, {
"num": "044",
"name": "Gloom"
}]
}, {
"id": 46,
"num": "046",
"name": "Paras",
"img": "http://www.serebii.net/pokemongo/pokemon/046.png",
"type": [
"Bug",
"Grass"
],
"height": "0.30 m",
"weight": "5.4 kg",
"candy": "Paras Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 2.36,
"avg_spawns": 236,
"spawn_time": "01:42",
"multipliers": [2.02],
"weaknesses": [
"Fire",
"Ice",
"Poison",
"Flying",
"Bug",
"Rock"
],
"next_evolution": [{
"num": "047",
"name": "Parasect"
}]
}, {
"id": 47,
"num": "047",
"name": "Parasect",
"img": "http://www.serebii.net/pokemongo/pokemon/047.png",
"type": [
"Bug",
"Grass"
],
"height": "0.99 m",
"weight": "29.5 kg",
"candy": "Paras Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.074,
"avg_spawns": 7.4,
"spawn_time": "01:22",
"multipliers": null,
"weaknesses": [
"Fire",
"Ice",
"Poison",
"Flying",
"Bug",
"Rock"
],
"prev_evolution": [{
"num": "046",
"name": "Paras"
}]
}, {
"id": 48,
"num": "048",
"name": "Venonat",
"img": "http://www.serebii.net/pokemongo/pokemon/048.png",
"type": [
"Bug",
"Poison"
],
"height": "0.99 m",
"weight": "30.0 kg",
"candy": "Venonat Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 2.28,
"avg_spawns": 228,
"spawn_time": "02:31",
"multipliers": [
1.86,
1.9
],
"weaknesses": [
"Fire",
"Flying",
"Psychic",
"Rock"
],
"next_evolution": [{
"num": "049",
"name": "Venomoth"
}]
}, {
"id": 49,
"num": "049",
"name": "Venomoth",
"img": "http://www.serebii.net/pokemongo/pokemon/049.png",
"type": [
"Bug",
"Poison"
],
"height": "1.50 m",
"weight": "12.5 kg",
"candy": "Venonat Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.072,
"avg_spawns": 7.2,
"spawn_time": "23:40",
"multipliers": null,
"weaknesses": [
"Fire",
"Flying",
"Psychic",
"Rock"
],
"prev_evolution": [{
"num": "048",
"name": "Venonat"
}]
}, {
"id": 50,
"num": "050",
"name": "Diglett",
"img": "http://www.serebii.net/pokemongo/pokemon/050.png",
"type": [
"Ground"
],
"height": "0.20 m",
"weight": "0.8 kg",
"candy": "Diglett Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.40,
"avg_spawns": 40,
"spawn_time": "02:22",
"multipliers": [2.69],
"weaknesses": [
"Water",
"Grass",
"Ice"
],
"next_evolution": [{
"num": "051",
"name": "Dugtrio"
}]
}, {
"id": 51,
"num": "051",
"name": "Dugtrio",
"img": "http://www.serebii.net/pokemongo/pokemon/051.png",
"type": [
"Ground"
],
"height": "0.71 m",
"weight": "33.3 kg",
"candy": "Dugtrio",
"egg": "Not in Eggs",
"spawn_chance": 0.014,
"avg_spawns": 1.4,
"spawn_time": "12:37",
"multipliers": null,
"weaknesses": [
"Water",
"Grass",
"Ice"
],
"prev_evolution": [{
"num": "050",
"name": "Diglett"
}]
}, {
"id": 52,
"num": "052",
"name": "Meowth",
"img": "http://www.serebii.net/pokemongo/pokemon/052.png",
"type": [
"Normal"
],
"height": "0.41 m",
"weight": "4.2 kg",
"candy": "Meowth Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.86,
"avg_spawns": 86,
"spawn_time": "02:54",
"multipliers": [1.98],
"weaknesses": [
"Fighting"
],
"next_evolution": [{
"num": "053",
"name": "Persian"
}]
}, {
"id": 53,
"num": "053",
"name": "Persian",
"img": "http://www.serebii.net/pokemongo/pokemon/053.png",
"type": [
"Normal"
],
"height": "0.99 m",
"weight": "32.0 kg",
"candy": "Meowth Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.022,
"avg_spawns": 2.2,
"spawn_time": "02:44",
"multipliers": null,
"weaknesses": [
"Fighting"
],
"prev_evolution": [{
"num": "052",
"name": "Meowth"
}]
}, {
"id": 54,
"num": "054",
"name": "Psyduck",
"img": "http://www.serebii.net/pokemongo/pokemon/054.png",
"type": [
"Water"
],
"height": "0.79 m",
"weight": "19.6 kg",
"candy": "Psyduck Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 2.54,
"avg_spawns": 254,
"spawn_time": "03:41",
"multipliers": [2.27],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "055",
"name": "Golduck"
}]
}, {
"id": 55,
"num": "055",
"name": "Golduck",
"img": "http://www.serebii.net/pokemongo/pokemon/055.png",
"type": [
"Water"
],
"height": "1.70 m",
"weight": "76.6 kg",
"candy": "Psyduck Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.087,
"avg_spawns": 8.7,
"spawn_time": "23:06",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "054",
"name": "Psyduck"
}]
}, {
"id": 56,
"num": "056",
"name": "Mankey",
"img": "http://www.serebii.net/pokemongo/pokemon/056.png",
"type": [
"Fighting"
],
"height": "0.51 m",
"weight": "28.0 kg",
"candy": "Mankey Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.92,
"avg_spawns": 92,
"spawn_time": "12:52",
"multipliers": [
2.17,
2.28
],
"weaknesses": [
"Flying",
"Psychic",
"Fairy"
],
"next_evolution": [{
"num": "057",
"name": "Primeape"
}]
}, {
"id": 57,
"num": "057",
"name": "Primeape",
"img": "http://www.serebii.net/pokemongo/pokemon/057.png",
"type": [
"Fighting"
],
"height": "0.99 m",
"weight": "32.0 kg",
"candy": "Mankey Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.031,
"avg_spawns": 3.1,
"spawn_time": "12:33",
"multipliers": null,
"weaknesses": [
"Flying",
"Psychic",
"Fairy"
],
"prev_evolution": [{
"num": "056",
"name": "Mankey"
}]
}, {
"id": 58,
"num": "058",
"name": "Growlithe",
"img": "http://www.serebii.net/pokemongo/pokemon/058.png",
"type": [
"Fire"
],
"height": "0.71 m",
"weight": "19.0 kg",
"candy": "Growlithe Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.92,
"avg_spawns": 92,
"spawn_time": "03:57",
"multipliers": [
2.31,
2.36
],
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"next_evolution": [{
"num": "059",
"name": "Arcanine"
}]
}, {
"id": 59,
"num": "059",
"name": "Arcanine",
"img": "http://www.serebii.net/pokemongo/pokemon/059.png",
"type": [
"Fire"
],
"height": "1.91 m",
"weight": "155.0 kg",
"candy": "Growlithe Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.017,
"avg_spawns": 1.7,
"spawn_time": "03:11",
"multipliers": null,
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"prev_evolution": [{
"num": "058",
"name": "Growlithe"
}]
}, {
"id": 60,
"num": "060",
"name": "Poliwag",
"img": "http://www.serebii.net/pokemongo/pokemon/060.png",
"type": [
"Water"
],
"height": "0.61 m",
"weight": "12.4 kg",
"candy": "Poliwag Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 2.19,
"avg_spawns": 219,
"spawn_time": "03:40",
"multipliers": [
1.72,
1.73
],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "061",
"name": "Poliwhirl"
}, {
"num": "062",
"name": "Poliwrath"
}]
}, {
"id": 61,
"num": "061",
"name": "Poliwhirl",
"img": "http://www.serebii.net/pokemongo/pokemon/061.png",
"type": [
"Water"
],
"height": "0.99 m",
"weight": "20.0 kg",
"candy": "Poliwag Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.13,
"avg_spawns": 13,
"spawn_time": "09:14",
"multipliers": [1.95],
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "060",
"name": "Poliwag"
}],
"next_evolution": [{
"num": "062",
"name": "Poliwrath"
}]
}, {
"id": 62,
"num": "062",
"name": "Poliwrath",
"img": "http://www.serebii.net/pokemongo/pokemon/062.png",
"type": [
"Water",
"Fighting"
],
"height": "1.30 m",
"weight": "54.0 kg",
"candy": "Poliwag Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.011,
"avg_spawns": 1.1,
"spawn_time": "01:32",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Flying",
"Psychic",
"Fairy"
],
"prev_evolution": [{
"num": "060",
"name": "Poliwag"
}, {
"num": "061",
"name": "Poliwhirl"
}]
}, {
"id": 63,
"num": "063",
"name": "Abra",
"img": "http://www.serebii.net/pokemongo/pokemon/063.png",
"type": [
"Psychic"
],
"height": "0.89 m",
"weight": "19.5 kg",
"candy": "Abra Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 0.42,
"avg_spawns": 42,
"spawn_time": "04:30",
"multipliers": [
1.36,
1.95
],
"weaknesses": [
"Bug",
"Ghost",
"Dark"
],
"next_evolution": [{
"num": "064",
"name": "Kadabra"
}, {
"num": "065",
"name": "Alakazam"
}]
}, {
"id": 64,
"num": "064",
"name": "Kadabra",
"img": "http://www.serebii.net/pokemongo/pokemon/064.png",
"type": [
"Psychic"
],
"height": "1.30 m",
"weight": "56.5 kg",
"candy": "Abra Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.027,
"avg_spawns": 2.7,
"spawn_time": "11:25",
"multipliers": [1.4],
"weaknesses": [
"Bug",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "063",
"name": "Abra"
}],
"next_evolution": [{
"num": "065",
"name": "Alakazam"
}]
}, {
"id": 65,
"num": "065",
"name": "Alakazam",
"img": "http://www.serebii.net/pokemongo/pokemon/065.png",
"type": [
"Psychic"
],
"height": "1.50 m",
"weight": "48.0 kg",
"candy": "Abra Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0073,
"avg_spawns": 0.73,
"spawn_time": "12:33",
"multipliers": null,
"weaknesses": [
"Bug",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "063",
"name": "Abra"
}, {
"num": "064",
"name": "Kadabra"
}]
}, {
"id": 66,
"num": "066",
"name": "Machop",
"img": "http://www.serebii.net/pokemongo/pokemon/066.png",
"type": [
"Fighting"
],
"height": "0.79 m",
"weight": "19.5 kg",
"candy": "Machop Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 0.49,
"avg_spawns": 49,
"spawn_time": "01:55",
"multipliers": [
1.64,
1.65
],
"weaknesses": [
"Flying",
"Psychic",
"Fairy"
],
"next_evolution": [{
"num": "067",
"name": "Machoke"
}, {
"num": "068",
"name": "Machamp"
}]
}, {
"id": 67,
"num": "067",
"name": "Machoke",
"img": "http://www.serebii.net/pokemongo/pokemon/067.png",
"type": [
"Fighting"
],
"height": "1.50 m",
"weight": "70.5 kg",
"candy": "Machop Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.034,
"avg_spawns": 3.4,
"spawn_time": "10:32",
"multipliers": [1.7],
"weaknesses": [
"Flying",
"Psychic",
"Fairy"
],
"prev_evolution": [{
"num": "066",
"name": "Machop"
}],
"next_evolution": [{
"num": "068",
"name": "Machamp"
}]
}, {
"id": 68,
"num": "068",
"name": "Machamp",
"img": "http://www.serebii.net/pokemongo/pokemon/068.png",
"type": [
"Fighting"
],
"height": "1.60 m",
"weight": "130.0 kg",
"candy": "Machop Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0068,
"avg_spawns": 0.68,
"spawn_time": "02:55",
"multipliers": null,
"weaknesses": [
"Flying",
"Psychic",
"Fairy"
],
"prev_evolution": [{
"num": "066",
"name": "Machop"
}, {
"num": "067",
"name": "Machoke"
}]
}, {
"id": 69,
"num": "069",
"name": "Bellsprout",
"img": "http://www.serebii.net/pokemongo/pokemon/069.png",
"type": [
"Grass",
"Poison"
],
"height": "0.71 m",
"weight": "4.0 kg",
"candy": "Bellsprout Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 1.15,
"avg_spawns": 115,
"spawn_time": "04:10",
"multipliers": [1.57],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"next_evolution": [{
"num": "070",
"name": "Weepinbell"
}, {
"num": "071",
"name": "Victreebel"
}]
}, {
"id": 70,
"num": "070",
"name": "Weepinbell",
"img": "http://www.serebii.net/pokemongo/pokemon/070.png",
"type": [
"Grass",
"Poison"
],
"height": "0.99 m",
"weight": "6.4 kg",
"candy": "Bellsprout Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.072,
"avg_spawns": 7.2,
"spawn_time": "09:45",
"multipliers": [1.59],
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"prev_evolution": [{
"num": "069",
"name": "Bellsprout"
}],
"next_evolution": [{
"num": "071",
"name": "Victreebel"
}]
}, {
"id": 71,
"num": "071",
"name": "Victreebel",
"img": "http://www.serebii.net/pokemongo/pokemon/071.png",
"type": [
"Grass",
"Poison"
],
"height": "1.70 m",
"weight": "15.5 kg",
"candy": "Bellsprout Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0059,
"avg_spawns": 0.59,
"spawn_time": "12:19",
"multipliers": null,
"weaknesses": [
"Fire",
"Ice",
"Flying",
"Psychic"
],
"prev_evolution": [{
"num": "069",
"name": "Bellsprout"
}, {
"num": "070",
"name": "Weepinbell"
}]
}, {
"id": 72,
"num": "072",
"name": "Tentacool",
"img": "http://www.serebii.net/pokemongo/pokemon/072.png",
"type": [
"Water",
"Poison"
],
"height": "0.89 m",
"weight": "45.5 kg",
"candy": "Tentacool Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.81,
"avg_spawns": 81,
"spawn_time": "03:20",
"multipliers": [2.52],
"weaknesses": [
"Electric",
"Ground",
"Psychic"
],
"next_evolution": [{
"num": "073",
"name": "Tentacruel"
}]
}, {
"id": 73,
"num": "073",
"name": "Tentacruel",
"img": "http://www.serebii.net/pokemongo/pokemon/073.png",
"type": [
"Water",
"Poison"
],
"height": "1.60 m",
"weight": "55.0 kg",
"candy": "Tentacool Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.082,
"avg_spawns": 8.2,
"spawn_time": "23:36",
"multipliers": null,
"weaknesses": [
"Electric",
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "072",
"name": "Tentacool"
}]
}, {
"id": 74,
"num": "074",
"name": "Geodude",
"img": "http://www.serebii.net/pokemongo/pokemon/074.png",
"type": [
"Rock",
"Ground"
],
"height": "0.41 m",
"weight": "20.0 kg",
"candy": "Geodude Candy",
"candy_count": 25,
"egg": "2 km",
"spawn_chance": 1.19,
"avg_spawns": 119,
"spawn_time": "12:40",
"multipliers": [
1.75,
1.76
],
"weaknesses": [
"Water",
"Grass",
"Ice",
"Fighting",
"Ground",
"Steel"
],
"next_evolution": [{
"num": "075",
"name": "Graveler"
}, {
"num": "076",
"name": "Golem"
}]
}, {
"id": 75,
"num": "075",
"name": "Graveler",
"img": "http://www.serebii.net/pokemongo/pokemon/075.png",
"type": [
"Rock",
"Ground"
],
"height": "0.99 m",
"weight": "105.0 kg",
"candy": "Geodude Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.071,
"avg_spawns": 7.1,
"spawn_time": "04:53",
"multipliers": [
1.64,
1.72
],
"weaknesses": [
"Water",
"Grass",
"Ice",
"Fighting",
"Ground",
"Steel"
],
"prev_evolution": [{
"num": "074",
"name": "Geodude"
}],
"next_evolution": [{
"num": "076",
"name": "Golem"
}]
}, {
"id": 76,
"num": "076",
"name": "Golem",
"img": "http://www.serebii.net/pokemongo/pokemon/076.png",
"type": [
"Rock",
"Ground"
],
"height": "1.40 m",
"weight": "300.0 kg",
"candy": "Geodude Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0047,
"avg_spawns": 0.47,
"spawn_time": "12:16",
"multipliers": null,
"weaknesses": [
"Water",
"Grass",
"Ice",
"Fighting",
"Ground",
"Steel"
],
"prev_evolution": [{
"num": "074",
"name": "Geodude"
}, {
"num": "075",
"name": "Graveler"
}]
}, {
"id": 77,
"num": "077",
"name": "Ponyta",
"img": "http://www.serebii.net/pokemongo/pokemon/077.png",
"type": [
"Fire"
],
"height": "0.99 m",
"weight": "30.0 kg",
"candy": "Ponyta Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.51,
"avg_spawns": 51,
"spawn_time": "02:50",
"multipliers": [
1.48,
1.5
],
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"next_evolution": [{
"num": "078",
"name": "Rapidash"
}]
}, {
"id": 78,
"num": "078",
"name": "Rapidash",
"img": "http://www.serebii.net/pokemongo/pokemon/078.png",
"type": [
"Fire"
],
"height": "1.70 m",
"weight": "95.0 kg",
"candy": "Ponyta Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.011,
"avg_spawns": 1.1,
"spawn_time": "04:00",
"multipliers": null,
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"prev_evolution": [{
"num": "077",
"name": "Ponyta"
}]
}, {
"id": 79,
"num": "079",
"name": "Slowpoke",
"img": "http://www.serebii.net/pokemongo/pokemon/079.png",
"type": [
"Water",
"Psychic"
],
"height": "1.19 m",
"weight": "36.0 kg",
"candy": "Slowpoke Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 1.05,
"avg_spawns": 105,
"spawn_time": "07:12",
"multipliers": [2.21],
"weaknesses": [
"Electric",
"Grass",
"Bug",
"Ghost",
"Dark"
],
"next_evolution": [{
"num": "080",
"name": "Slowbro"
}]
}, {
"id": 80,
"num": "080",
"name": "Slowbro",
"img": "http://www.serebii.net/pokemongo/pokemon/080.png",
"type": [
"Water",
"Psychic"
],
"height": "1.60 m",
"weight": "78.5 kg",
"candy": "Slowpoke Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.036,
"avg_spawns": 3.6,
"spawn_time": "02:56",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Bug",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "079",
"name": "Slowpoke"
}]
}, {
"id": 81,
"num": "081",
"name": "Magnemite",
"img": "http://www.serebii.net/pokemongo/pokemon/081.png",
"type": [
"Electric"
],
"height": "0.30 m",
"weight": "6.0 kg",
"candy": "Magnemite Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.71,
"avg_spawns": 71,
"spawn_time": "04:04",
"multipliers": [
2.16,
2.17
],
"weaknesses": [
"Fire",
"Water",
"Ground"
],
"next_evolution": [{
"num": "082",
"name": "Magneton"
}]
}, {
"id": 82,
"num": "082",
"name": "Magneton",
"img": "http://www.serebii.net/pokemongo/pokemon/082.png",
"type": [
"Electric"
],
"height": "0.99 m",
"weight": "60.0 kg",
"candy": "Magnemite Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.023,
"avg_spawns": 2.3,
"spawn_time": "15:25",
"multipliers": null,
"weaknesses": [
"Fire",
"Water",
"Ground"
],
"prev_evolution": [{
"num": "081",
"name": "Magnemite"
}]
}, {
"id": 83,
"num": "083",
"name": "Farfetch'd",
"img": "http://www.serebii.net/pokemongo/pokemon/083.png",
"type": [
"Normal",
"Flying"
],
"height": "0.79 m",
"weight": "15.0 kg",
"candy": "None",
"egg": "5 km",
"spawn_chance": 0.0212,
"avg_spawns": 2.12,
"spawn_time": "01:09",
"multipliers": null,
"weaknesses": [
"Electric",
"Rock"
]
}, {
"id": 84,
"num": "084",
"name": "Doduo",
"img": "http://www.serebii.net/pokemongo/pokemon/084.png",
"type": [
"Normal",
"Flying"
],
"height": "1.40 m",
"weight": "39.2 kg",
"candy": "Doduo Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.52,
"avg_spawns": 52,
"spawn_time": "05:10",
"multipliers": [
2.19,
2.24
],
"weaknesses": [
"Electric",
"Rock"
],
"next_evolution": [{
"num": "085",
"name": "Dodrio"
}]
}, {
"id": 85,
"num": "085",
"name": "Dodrio",
"img": "http://www.serebii.net/pokemongo/pokemon/085.png",
"type": [
"Normal",
"Flying"
],
"height": "1.80 m",
"weight": "85.2 kg",
"candy": "Doduo Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.22,
"avg_spawns": 22,
"spawn_time": "02:12",
"multipliers": null,
"weaknesses": [
"Electric",
"Rock"
],
"prev_evolution": [{
"num": "084",
"name": "Doduo"
}]
}, {
"id": 86,
"num": "086",
"name": "Seel",
"img": "http://www.serebii.net/pokemongo/pokemon/086.png",
"type": [
"Water"
],
"height": "1.09 m",
"weight": "90.0 kg",
"candy": "Seel Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.28,
"avg_spawns": 28,
"spawn_time": "06:46",
"multipliers": [
1.04,
1.96
],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "087",
"name": "Dewgong"
}]
}, {
"id": 87,
"num": "087",
"name": "Dewgong",
"img": "http://www.serebii.net/pokemongo/pokemon/087.png",
"type": [
"Water",
"Ice"
],
"height": "1.70 m",
"weight": "120.0 kg",
"candy": "Seel Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.013,
"avg_spawns": 1.3,
"spawn_time": "06:04",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Fighting",
"Rock"
],
"prev_evolution": [{
"num": "086",
"name": "Seel"
}]
}, {
"id": 88,
"num": "088",
"name": "Grimer",
"img": "http://www.serebii.net/pokemongo/pokemon/088.png",
"type": [
"Poison"
],
"height": "0.89 m",
"weight": "30.0 kg",
"candy": "Grimer Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.052,
"avg_spawns": 5.2,
"spawn_time": "15:11",
"multipliers": [2.44],
"weaknesses": [
"Ground",
"Psychic"
],
"next_evolution": [{
"num": "089",
"name": "Muk"
}]
}, {
"id": 89,
"num": "089",
"name": "Muk",
"img": "http://www.serebii.net/pokemongo/pokemon/089.png",
"type": [
"Poison"
],
"height": "1.19 m",
"weight": "30.0 kg",
"candy": "Grimer Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0031,
"avg_spawns": 0.31,
"spawn_time": "01:28",
"multipliers": null,
"weaknesses": [
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "088",
"name": "Grimer"
}]
}, {
"id": 90,
"num": "090",
"name": "Shellder",
"img": "http://www.serebii.net/pokemongo/pokemon/090.png",
"type": [
"Water"
],
"height": "0.30 m",
"weight": "4.0 kg",
"candy": "Shellder Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.52,
"avg_spawns": 52,
"spawn_time": "07:39",
"multipliers": [2.65],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "091",
"name": "Cloyster"
}]
}, {
"id": 91,
"num": "091",
"name": "Cloyster",
"img": "http://www.serebii.net/pokemongo/pokemon/091.png",
"type": [
"Water",
"Ice"
],
"height": "1.50 m",
"weight": "132.5 kg",
"candy": "Shellder Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.015,
"avg_spawns": 1.5,
"spawn_time": "02:33",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Fighting",
"Rock"
],
"prev_evolution": [{
"num": "090",
"name": "Shellder"
}]
}, {
"id": 92,
"num": "092",
"name": "Gastly",
"img": "http://www.serebii.net/pokemongo/pokemon/092.png",
"type": [
"Ghost",
"Poison"
],
"height": "1.30 m",
"weight": "0.1 kg",
"candy": "Gastly Candy",
"candy_count": 25,
"egg": "5 km",
"spawn_chance": 0.79,
"avg_spawns": 79,
"spawn_time": "04:21",
"multipliers": [1.78],
"weaknesses": [
"Ground",
"Psychic",
"Ghost",
"Dark"
],
"next_evolution": [{
"num": "093",
"name": "Haunter"
}, {
"num": "094",
"name": "Gengar"
}]
}, {
"id": 93,
"num": "093",
"name": "Haunter",
"img": "http://www.serebii.net/pokemongo/pokemon/093.png",
"type": [
"Ghost",
"Poison"
],
"height": "1.60 m",
"weight": "0.1 kg",
"candy": "Gastly Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.052,
"avg_spawns": 5.2,
"spawn_time": "00:10",
"multipliers": [
1.56,
1.8
],
"weaknesses": [
"Ground",
"Psychic",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "092",
"name": "Gastly"
}],
"next_evolution": [{
"num": "094",
"name": "Gengar"
}]
}, {
"id": 94,
"num": "094",
"name": "Gengar",
"img": "http://www.serebii.net/pokemongo/pokemon/094.png",
"type": [
"Ghost",
"Poison"
],
"height": "1.50 m",
"weight": "40.5 kg",
"candy": "Gastly Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0067,
"avg_spawns": 0.67,
"spawn_time": "03:55",
"multipliers": null,
"weaknesses": [
"Ground",
"Psychic",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "092",
"name": "Gastly"
}, {
"num": "093",
"name": "Haunter"
}]
}, {
"id": 95,
"num": "095",
"name": "Onix",
"img": "http://www.serebii.net/pokemongo/pokemon/095.png",
"type": [
"Rock",
"Ground"
],
"height": "8.79 m",
"weight": "210.0 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.10,
"avg_spawns": 10,
"spawn_time": "01:18",
"multipliers": null,
"weaknesses": [
"Water",
"Grass",
"Ice",
"Fighting",
"Ground",
"Steel"
]
}, {
"id": 96,
"num": "096",
"name": "Drowzee",
"img": "http://www.serebii.net/pokemongo/pokemon/096.png",
"type": [
"Psychic"
],
"height": "0.99 m",
"weight": "32.4 kg",
"candy": "Drowzee Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 3.21,
"avg_spawns": 321,
"spawn_time": "01:51",
"multipliers": [
2.08,
2.09
],
"weaknesses": [
"Bug",
"Ghost",
"Dark"
],
"next_evolution": [{
"num": "097",
"name": "Hypno"
}]
}, {
"id": 97,
"num": "097",
"name": "Hypno",
"img": "http://www.serebii.net/pokemongo/pokemon/097.png",
"type": [
"Psychic"
],
"height": "1.60 m",
"weight": "75.6 kg",
"candy": "Drowzee Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.10,
"avg_spawns": 10,
"spawn_time": "02:17",
"multipliers": null,
"weaknesses": [
"Bug",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "096",
"name": "Drowzee"
}]
}, {
"id": 98,
"num": "098",
"name": "Krabby",
"img": "http://www.serebii.net/pokemongo/pokemon/098.png",
"type": [
"Water"
],
"height": "0.41 m",
"weight": "6.5 kg",
"candy": "Krabby Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 2.12,
"avg_spawns": 212,
"spawn_time": "03:33",
"multipliers": [
2.36,
2.4
],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "099",
"name": "Kingler"
}]
}, {
"id": 99,
"num": "099",
"name": "Kingler",
"img": "http://www.serebii.net/pokemongo/pokemon/099.png",
"type": [
"Water"
],
"height": "1.30 m",
"weight": "60.0 kg",
"candy": "Krabby Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.062,
"avg_spawns": 6.2,
"spawn_time": "03:44",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "098",
"name": "Krabby"
}]
}, {
"id": 100,
"num": "100",
"name": "Voltorb",
"img": "http://www.serebii.net/pokemongo/pokemon/100.png",
"type": [
"Electric"
],
"height": "0.51 m",
"weight": "10.4 kg",
"candy": "Voltorb Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.65,
"avg_spawns": 65,
"spawn_time": "04:36",
"multipliers": [
2.01,
2.02
],
"weaknesses": [
"Ground"
],
"next_evolution": [{
"num": "101",
"name": "Electrode"
}]
}, {
"id": 101,
"num": "101",
"name": "Electrode",
"img": "http://www.serebii.net/pokemongo/pokemon/101.png",
"type": [
"Electric"
],
"height": "1.19 m",
"weight": "66.6 kg",
"candy": "Voltorb Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.02,
"avg_spawns": 2,
"spawn_time": "04:10",
"multipliers": null,
"weaknesses": [
"Ground"
],
"prev_evolution": [{
"num": "100",
"name": "Voltorb"
}]
}, {
"id": 102,
"num": "102",
"name": "Exeggcute",
"img": "http://www.serebii.net/pokemongo/pokemon/102.png",
"type": [
"Grass",
"Psychic"
],
"height": "0.41 m",
"weight": "2.5 kg",
"candy": "Exeggcute Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.78,
"avg_spawns": 78,
"spawn_time": "09:09",
"multipliers": [
2.7,
3.18
],
"weaknesses": [
"Fire",
"Ice",
"Poison",
"Flying",
"Bug",
"Ghost",
"Dark"
],
"next_evolution": [{
"num": "103",
"name": "Exeggutor"
}]
}, {
"id": 103,
"num": "103",
"name": "Exeggutor",
"img": "http://www.serebii.net/pokemongo/pokemon/103.png",
"type": [
"Grass",
"Psychic"
],
"height": "2.01 m",
"weight": "120.0 kg",
"candy": "Exeggcute Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.014,
"avg_spawns": 1.4,
"spawn_time": "12:34",
"multipliers": null,
"weaknesses": [
"Fire",
"Ice",
"Poison",
"Flying",
"Bug",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "102",
"name": "Exeggcute"
}]
}, {
"id": 104,
"num": "104",
"name": "Cubone",
"img": "http://www.serebii.net/pokemongo/pokemon/104.png",
"type": [
"Ground"
],
"height": "0.41 m",
"weight": "6.5 kg",
"candy": "Cubone Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.61,
"avg_spawns": 61,
"spawn_time": "01:51",
"multipliers": [1.67],
"weaknesses": [
"Water",
"Grass",
"Ice"
],
"next_evolution": [{
"num": "105",
"name": "Marowak"
}]
}, {
"id": 105,
"num": "105",
"name": "Marowak",
"img": "http://www.serebii.net/pokemongo/pokemon/105.png",
"type": [
"Ground"
],
"height": "0.99 m",
"weight": "45.0 kg",
"candy": "Cubone Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.02,
"avg_spawns": 2,
"spawn_time": "03:59",
"multipliers": null,
"weaknesses": [
"Water",
"Grass",
"Ice"
],
"prev_evolution": [{
"num": "104",
"name": "Cubone"
}]
}, {
"id": 106,
"num": "106",
"name": "Hitmonlee",
"img": "http://www.serebii.net/pokemongo/pokemon/106.png",
"type": [
"Fighting"
],
"height": "1.50 m",
"weight": "49.8 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.02,
"avg_spawns": 2,
"spawn_time": "03:59",
"multipliers": null,
"weaknesses": [
"Flying",
"Psychic",
"Fairy"
]
}, {
"id": 107,
"num": "107",
"name": "Hitmonchan",
"img": "http://www.serebii.net/pokemongo/pokemon/107.png",
"type": [
"Fighting"
],
"height": "1.40 m",
"weight": "50.2 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.022,
"avg_spawns": 2.2,
"spawn_time": "05:58",
"multipliers": null,
"weaknesses": [
"Flying",
"Psychic",
"Fairy"
]
}, {
"id": 108,
"num": "108",
"name": "Lickitung",
"img": "http://www.serebii.net/pokemongo/pokemon/108.png",
"type": [
"Normal"
],
"height": "1.19 m",
"weight": "65.5 kg",
"candy": "None",
"egg": "5 km",
"spawn_chance": 0.011,
"avg_spawns": 1.1,
"spawn_time": "02:46",
"multipliers": null,
"weaknesses": [
"Fighting"
]
}, {
"id": 109,
"num": "109",
"name": "Koffing",
"img": "http://www.serebii.net/pokemongo/pokemon/109.png",
"type": [
"Poison"
],
"height": "0.61 m",
"weight": "1.0 kg",
"candy": "Koffing Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.20,
"avg_spawns": 20,
"spawn_time": "08:16",
"multipliers": [1.11],
"weaknesses": [
"Ground",
"Psychic"
],
"next_evolution": [{
"num": "110",
"name": "Weezing"
}]
}, {
"id": 110,
"num": "110",
"name": "Weezing",
"img": "http://www.serebii.net/pokemongo/pokemon/110.png",
"type": [
"Poison"
],
"height": "1.19 m",
"weight": "9.5 kg",
"candy": "Koffing Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.016,
"avg_spawns": 1.6,
"spawn_time": "12:17",
"multipliers": null,
"weaknesses": [
"Ground",
"Psychic"
],
"prev_evolution": [{
"num": "109",
"name": "Koffing"
}]
}, {
"id": 111,
"num": "111",
"name": "Rhyhorn",
"img": "http://www.serebii.net/pokemongo/pokemon/111.png",
"type": [
"Ground",
"Rock"
],
"height": "0.99 m",
"weight": "115.0 kg",
"candy": "Rhyhorn Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 0.63,
"avg_spawns": 63,
"spawn_time": "03:21",
"multipliers": [1.91],
"weaknesses": [
"Water",
"Grass",
"Ice",
"Fighting",
"Ground",
"Steel"
],
"next_evolution": [{
"num": "112",
"name": "Rhydon"
}]
}, {
"id": 112,
"num": "112",
"name": "Rhydon",
"img": "http://www.serebii.net/pokemongo/pokemon/112.png",
"type": [
"Ground",
"Rock"
],
"height": "1.91 m",
"weight": "120.0 kg",
"candy": "Rhyhorn Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.022,
"avg_spawns": 2.2,
"spawn_time": "05:50",
"multipliers": null,
"weaknesses": [
"Water",
"Grass",
"Ice",
"Fighting",
"Ground",
"Steel"
],
"prev_evolution": [{
"num": "111",
"name": "Rhyhorn"
}]
}, {
"id": 113,
"num": "113",
"name": "Chansey",
"img": "http://www.serebii.net/pokemongo/pokemon/113.png",
"type": [
"Normal"
],
"height": "1.09 m",
"weight": "34.6 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.013,
"avg_spawns": 1.3,
"spawn_time": "04:46",
"multipliers": null,
"weaknesses": [
"Fighting"
]
}, {
"id": 114,
"num": "114",
"name": "Tangela",
"img": "http://www.serebii.net/pokemongo/pokemon/114.png",
"type": [
"Grass"
],
"height": "0.99 m",
"weight": "35.0 kg",
"candy": "None",
"egg": "5 km",
"spawn_chance": 0.228,
"avg_spawns": 22.8,
"spawn_time": "23:13",
"multipliers": null,
"weaknesses": [
"Fire",
"Ice",
"Poison",
"Flying",
"Bug"
]
}, {
"id": 115,
"num": "115",
"name": "Kangaskhan",
"img": "http://www.serebii.net/pokemongo/pokemon/115.png",
"type": [
"Normal"
],
"height": "2.21 m",
"weight": "80.0 kg",
"candy": "None",
"egg": "5 km",
"spawn_chance": 0.0086,
"avg_spawns": 0.86,
"spawn_time": "02:40",
"multipliers": null,
"weaknesses": [
"Fighting"
]
}, {
"id": 116,
"num": "116",
"name": "Horsea",
"img": "http://www.serebii.net/pokemongo/pokemon/116.png",
"type": [
"Water"
],
"height": "0.41 m",
"weight": "8.0 kg",
"candy": "Horsea Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 1.13,
"avg_spawns": 113,
"spawn_time": "02:53",
"multipliers": [2.23],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "117",
"name": "Seadra"
}]
}, {
"id": 117,
"num": "117",
"name": "Seadra",
"img": "http://www.serebii.net/pokemongo/pokemon/117.png",
"type": [
"Water"
],
"height": "1.19 m",
"weight": "25.0 kg",
"candy": "Horsea Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.034,
"avg_spawns": 3.4,
"spawn_time": "03:18",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "116",
"name": "Horsea"
}]
}, {
"id": 118,
"num": "118",
"name": "Goldeen",
"img": "http://www.serebii.net/pokemongo/pokemon/118.png",
"type": [
"Water"
],
"height": "0.61 m",
"weight": "15.0 kg",
"candy": "Goldeen Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 2.18,
"avg_spawns": 218,
"spawn_time": "03:14",
"multipliers": [
2.15,
2.2
],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "119",
"name": "Seaking"
}]
}, {
"id": 119,
"num": "119",
"name": "Seaking",
"img": "http://www.serebii.net/pokemongo/pokemon/119.png",
"type": [
"Water"
],
"height": "1.30 m",
"weight": "39.0 kg",
"candy": "Goldeen Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.08,
"avg_spawns": 8,
"spawn_time": "05:21",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "118",
"name": "Goldeen"
}]
}, {
"id": 120,
"num": "120",
"name": "Staryu",
"img": "http://www.serebii.net/pokemongo/pokemon/120.png",
"type": [
"Water"
],
"height": "0.79 m",
"weight": "34.5 kg",
"candy": "Staryu Candy",
"candy_count": 50,
"egg": "5 km",
"spawn_chance": 1.95,
"avg_spawns": 195,
"spawn_time": "22:59",
"multipliers": [
2.38,
2.41
],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "121",
"name": "Starmie"
}]
}, {
"id": 121,
"num": "121",
"name": "Starmie",
"img": "http://www.serebii.net/pokemongo/pokemon/121.png",
"type": [
"Water",
"Psychic"
],
"height": "1.09 m",
"weight": "80.0 kg",
"candy": "Staryu Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.034,
"avg_spawns": 3.4,
"spawn_time": "06:57",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Bug",
"Ghost",
"Dark"
],
"prev_evolution": [{
"num": "120",
"name": "Staryu"
}]
}, {
"id": 122,
"num": "122",
"name": "Mr. Mime",
"img": "http://www.serebii.net/pokemongo/pokemon/122.png",
"type": [
"Psychic"
],
"height": "1.30 m",
"weight": "54.5 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.0031,
"avg_spawns": 0.31,
"spawn_time": "01:51",
"multipliers": null,
"weaknesses": [
"Bug",
"Ghost",
"Dark"
]
}, {
"id": 123,
"num": "123",
"name": "Scyther",
"img": "http://www.serebii.net/pokemongo/pokemon/123.png",
"type": [
"Bug",
"Flying"
],
"height": "1.50 m",
"weight": "56.0 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.14,
"avg_spawns": 14,
"spawn_time": "05:43",
"multipliers": null,
"weaknesses": [
"Fire",
"Electric",
"Ice",
"Flying",
"Rock"
]
}, {
"id": 124,
"num": "124",
"name": "Jynx",
"img": "http://www.serebii.net/pokemongo/pokemon/124.png",
"type": [
"Ice",
"Psychic"
],
"height": "1.40 m",
"weight": "40.6 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.35,
"avg_spawns": 35,
"spawn_time": "05:41",
"multipliers": null,
"weaknesses": [
"Fire",
"Bug",
"Rock",
"Ghost",
"Dark",
"Steel"
]
}, {
"id": 125,
"num": "125",
"name": "Electabuzz",
"img": "http://www.serebii.net/pokemongo/pokemon/125.png",
"type": [
"Electric"
],
"height": "1.09 m",
"weight": "30.0 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.074,
"avg_spawns": 7.4,
"spawn_time": "04:28",
"multipliers": null,
"weaknesses": [
"Ground"
]
}, {
"id": 126,
"num": "126",
"name": "Magmar",
"img": "http://www.serebii.net/pokemongo/pokemon/126.png",
"type": [
"Fire"
],
"height": "1.30 m",
"weight": "44.5 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.10,
"avg_spawns": 10,
"spawn_time": "20:36",
"multipliers": null,
"weaknesses": [
"Water",
"Ground",
"Rock"
]
}, {
"id": 127,
"num": "127",
"name": "Pinsir",
"img": "http://www.serebii.net/pokemongo/pokemon/127.png",
"type": [
"Bug"
],
"height": "1.50 m",
"weight": "55.0 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.99,
"avg_spawns": 99,
"spawn_time": "03:25",
"multipliers": null,
"weaknesses": [
"Fire",
"Flying",
"Rock"
]
}, {
"id": 128,
"num": "128",
"name": "Tauros",
"img": "http://www.serebii.net/pokemongo/pokemon/128.png",
"type": [
"Normal"
],
"height": "1.40 m",
"weight": "88.4 kg",
"candy": "None",
"egg": "5 km",
"spawn_chance": 0.12,
"avg_spawns": 12,
"spawn_time": "00:37",
"multipliers": null,
"weaknesses": [
"Fighting"
]
}, {
"id": 129,
"num": "129",
"name": "Magikarp",
"img": "http://www.serebii.net/pokemongo/pokemon/129.png",
"type": [
"Water"
],
"height": "0.89 m",
"weight": "10.0 kg",
"candy": "Magikarp Candy",
"candy_count": 400,
"egg": "2 km",
"spawn_chance": 4.78,
"avg_spawns": 478,
"spawn_time": "14:26",
"multipliers": [
10.1,
11.8
],
"weaknesses": [
"Electric",
"Grass"
],
"next_evolution": [{
"num": "130",
"name": "Gyarados"
}]
}, {
"id": 130,
"num": "130",
"name": "Gyarados",
"img": "http://www.serebii.net/pokemongo/pokemon/130.png",
"type": [
"Water",
"Flying"
],
"height": "6.50 m",
"weight": "235.0 kg",
"candy": "Magikarp Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0032,
"avg_spawns": 0.32,
"spawn_time": "02:15",
"multipliers": null,
"weaknesses": [
"Electric",
"Rock"
],
"prev_evolution": [{
"num": "129",
"name": "Magikarp"
}]
}, {
"id": 131,
"num": "131",
"name": "Lapras",
"img": "http://www.serebii.net/pokemongo/pokemon/131.png",
"type": [
"Water",
"Ice"
],
"height": "2.49 m",
"weight": "220.0 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.006,
"avg_spawns": 0.6,
"spawn_time": "08:59",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Fighting",
"Rock"
]
}, {
"id": 132,
"num": "132",
"name": "Ditto",
"img": "http://www.serebii.net/pokemongo/pokemon/132.png",
"type": [
"Normal"
],
"height": "0.30 m",
"weight": "4.0 kg",
"candy": "None",
"egg": "Not in Eggs",
"spawn_chance": 0,
"avg_spawns": 0,
"spawn_time": "N/A",
"multipliers": null,
"weaknesses": [
"Fighting"
]
}, {
"id": 133,
"num": "133",
"name": "Eevee",
"img": "http://www.serebii.net/pokemongo/pokemon/133.png",
"type": [
"Normal"
],
"height": "0.30 m",
"weight": "6.5 kg",
"candy": "Eevee Candy",
"candy_count": 25,
"egg": "10 km",
"spawn_chance": 2.75,
"avg_spawns": 275,
"spawn_time": "05:32",
"multipliers": [
2.02,
2.64
],
"weaknesses": [
"Fighting"
],
"next_evolution": [{
"num": "134",
"name": "Vaporeon"
}, {
"num": "135",
"name": "Jolteon"
}, {
"num": "136",
"name": "Flareon"
}]
}, {
"id": 134,
"num": "134",
"name": "Vaporeon",
"img": "http://www.serebii.net/pokemongo/pokemon/134.png",
"type": [
"Water"
],
"height": "0.99 m",
"weight": "29.0 kg",
"candy": "Eevee Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.014,
"avg_spawns": 1.4,
"spawn_time": "10:54",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass"
],
"prev_evolution": [{
"num": "133",
"name": "Eevee"
}]
}, {
"id": 135,
"num": "135",
"name": "Jolteon",
"img": "http://www.serebii.net/pokemongo/pokemon/135.png",
"type": [
"Electric"
],
"height": "0.79 m",
"weight": "24.5 kg",
"candy": "None",
"egg": "Not in Eggs",
"spawn_chance": 0.012,
"avg_spawns": 1.2,
"spawn_time": "02:30",
"multipliers": null,
"weaknesses": [
"Ground"
],
"prev_evolution": [{
"num": "133",
"name": "Eevee"
}]
}, {
"id": 136,
"num": "136",
"name": "Flareon",
"img": "http://www.serebii.net/pokemongo/pokemon/136.png",
"type": [
"Fire"
],
"height": "0.89 m",
"weight": "25.0 kg",
"candy": "Eevee Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.017,
"avg_spawns": 1.7,
"spawn_time": "07:02",
"multipliers": null,
"weaknesses": [
"Water",
"Ground",
"Rock"
],
"prev_evolution": [{
"num": "133",
"name": "Eevee"
}]
}, {
"id": 137,
"num": "137",
"name": "Porygon",
"img": "http://www.serebii.net/pokemongo/pokemon/137.png",
"type": [
"Normal"
],
"height": "0.79 m",
"weight": "36.5 kg",
"candy": "None",
"egg": "5 km",
"spawn_chance": 0.012,
"avg_spawns": 1.2,
"spawn_time": "02:49",
"multipliers": null,
"weaknesses": [
"Fighting"
]
}, {
"id": 138,
"num": "138",
"name": "Omanyte",
"img": "http://www.serebii.net/pokemongo/pokemon/138.png",
"type": [
"Rock",
"Water"
],
"height": "0.41 m",
"weight": "7.5 kg",
"candy": "Omanyte Candy",
"candy_count": 50,
"egg": "10 km",
"spawn_chance": 0.14,
"avg_spawns": 14,
"spawn_time": "10:23",
"multipliers": [2.12],
"weaknesses": [
"Electric",
"Grass",
"Fighting",
"Ground"
],
"next_evolution": [{
"num": "139",
"name": "Omastar"
}]
}, {
"id": 139,
"num": "139",
"name": "Omastar",
"img": "http://www.serebii.net/pokemongo/pokemon/139.png",
"type": [
"Rock",
"Water"
],
"height": "0.99 m",
"weight": "35.0 kg",
"candy": "None",
"egg": "Omanyte Candy",
"spawn_chance": 0.0061,
"avg_spawns": 0.61,
"spawn_time": "05:04",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Fighting",
"Ground"
],
"prev_evolution": [{
"num": "138",
"name": "Omanyte"
}]
}, {
"id": 140,
"num": "140",
"name": "Kabuto",
"img": "http://www.serebii.net/pokemongo/pokemon/140.png",
"type": [
"Rock",
"Water"
],
"height": "0.51 m",
"weight": "11.5 kg",
"candy": "Kabuto Candy",
"candy_count": 50,
"egg": "10 km",
"spawn_chance": 0.10,
"avg_spawns": 10,
"spawn_time": "00:05",
"multipliers": [
1.97,
2.37
],
"weaknesses": [
"Electric",
"Grass",
"Fighting",
"Ground"
],
"next_evolution": [{
"num": "141",
"name": "Kabutops"
}]
}, {
"id": 141,
"num": "141",
"name": "Kabutops",
"img": "http://www.serebii.net/pokemongo/pokemon/141.png",
"type": [
"Rock",
"Water"
],
"height": "1.30 m",
"weight": "40.5 kg",
"candy": "Kabuto Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0032,
"avg_spawns": 0.32,
"spawn_time": "23:40",
"multipliers": null,
"weaknesses": [
"Electric",
"Grass",
"Fighting",
"Ground"
],
"prev_evolution": [{
"num": "140",
"name": "Kabuto"
}]
}, {
"id": 142,
"num": "142",
"name": "Aerodactyl",
"img": "http://www.serebii.net/pokemongo/pokemon/142.png",
"type": [
"Rock",
"Flying"
],
"height": "1.80 m",
"weight": "59.0 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.018,
"avg_spawns": 1.8,
"spawn_time": "23:40",
"multipliers": null,
"weaknesses": [
"Water",
"Electric",
"Ice",
"Rock",
"Steel"
]
}, {
"id": 143,
"num": "143",
"name": "Snorlax",
"img": "http://www.serebii.net/pokemongo/pokemon/143.png",
"type": [
"Normal"
],
"height": "2.11 m",
"weight": "460.0 kg",
"candy": "None",
"egg": "10 km",
"spawn_chance": 0.016,
"avg_spawns": 1.6,
"spawn_time": "23:40",
"multipliers": null,
"weaknesses": [
"Fighting"
]
}, {
"id": 144,
"num": "144",
"name": "Articuno",
"img": "http://www.serebii.net/pokemongo/pokemon/144.png",
"type": [
"Ice",
"Flying"
],
"height": "1.70 m",
"weight": "55.4 kg",
"candy": "None",
"egg": "Not in Eggs",
"spawn_chance": 0,
"avg_spawns": 0,
"spawn_time": "N/A",
"multipliers": null,
"weaknesses": [
"Fire",
"Electric",
"Rock",
"Steel"
]
}, {
"id": 145,
"num": "145",
"name": "Zapdos",
"img": "http://www.serebii.net/pokemongo/pokemon/145.png",
"type": [
"Electric",
"Flying"
],
"height": "1.60 m",
"weight": "52.6 kg",
"candy": "None",
"egg": "Not in Eggs",
"spawn_chance": 0,
"avg_spawns": 0,
"spawn_time": "N/A",
"multipliers": null,
"weaknesses": [
"Ice",
"Rock"
]
}, {
"id": 146,
"num": "146",
"name": "Moltres",
"img": "http://www.serebii.net/pokemongo/pokemon/146.png",
"type": [
"Fire",
"Flying"
],
"height": "2.01 m",
"weight": "60.0 kg",
"candy": "None",
"egg": "Not in Eggs",
"spawn_chance": 0,
"avg_spawns": 0,
"spawn_time": "N/A",
"multipliers": null,
"weaknesses": [
"Water",
"Electric",
"Rock"
]
}, {
"id": 147,
"num": "147",
"name": "Dratini",
"img": "http://www.serebii.net/pokemongo/pokemon/147.png",
"type": [
"Dragon"
],
"height": "1.80 m",
"weight": "3.3 kg",
"candy": "Dratini Candy",
"candy_count": 25,
"egg": "10 km",
"spawn_chance": 0.30,
"avg_spawns": 30,
"spawn_time": "06:41",
"multipliers": [
1.83,
1.84
],
"weaknesses": [
"Ice",
"Dragon",
"Fairy"
],
"next_evolution": [{
"num": "148",
"name": "Dragonair"
}, {
"num": "149",
"name": "Dragonite"
}]
}, {
"id": 148,
"num": "148",
"name": "Dragonair",
"img": "http://www.serebii.net/pokemongo/pokemon/148.png",
"type": [
"Dragon"
],
"height": "3.99 m",
"weight": "16.5 kg",
"candy": "Dratini Candy",
"candy_count": 100,
"egg": "Not in Eggs",
"spawn_chance": 0.02,
"avg_spawns": 2,
"spawn_time": "11:57",
"multipliers": [2.05],
"weaknesses": [
"Ice",
"Dragon",
"Fairy"
],
"prev_evolution": [{
"num": "147",
"name": "Dratini"
}],
"next_evolution": [{
"num": "149",
"name": "Dragonite"
}]
}, {
"id": 149,
"num": "149",
"name": "Dragonite",
"img": "http://www.serebii.net/pokemongo/pokemon/149.png",
"type": [
"Dragon",
"Flying"
],
"height": "2.21 m",
"weight": "210.0 kg",
"candy": "Dratini Candy",
"egg": "Not in Eggs",
"spawn_chance": 0.0011,
"avg_spawns": 0.11,
"spawn_time": "23:38",
"multipliers": null,
"weaknesses": [
"Ice",
"Rock",
"Dragon",
"Fairy"
],
"prev_evolution": [{
"num": "147",
"name": "Dratini"
}, {
"num": "148",
"name": "Dragonair"
}]
}, {
"id": 150,
"num": "150",
"name": "Mewtwo",
"img": "http://www.serebii.net/pokemongo/pokemon/150.png",
"type": [
"Psychic"
],
"height": "2.01 m",
"weight": "122.0 kg",
"candy": "None",
"egg": "Not in Eggs",
"spawn_chance": 0,
"avg_spawns": 0,
"spawn_time": "N/A",
"multipliers": null,
"weaknesses": [
"Bug",
"Ghost",
"Dark"
]
}, {
"id": 151,
"num": "151",
"name": "Mew",
"img": "http://www.serebii.net/pokemongo/pokemon/151.png",
"type": [
"Psychic"
],
"height": "0.41 m",
"weight": "4.0 kg",
"candy": "None",
"egg": "Not in Eggs",
"spawn_chance": 0,
"avg_spawns": 0,
"spawn_time": "N/A",
"multipliers": null,
"weaknesses": [
"Bug",
"Ghost",
"Dark"
]
}]
}
This is obviously not impossible, but it makes for a rather **unreadable** and difficult to maintain.

A configmap in HCL

Now... the advantage of Terraform is to be able to separate the manifest from the data.

resource "kubernetes_config_map" "data_user" {
metadata {
name = "data-user"
namespace = "hcl"
}

data = {
"data.ini" = "${file("${path.module}/data.ini")}"
}
}

The HCL is no match for Kubernetes' YAML.

What about a deployment?

In a good ecosystem, we deploy via Helm, let's see how Hashicorp presents it.

What is Helm?

Helm is a YAML templating tool similar to Jinja2, it is used in deployments requiring many YAML files (service, deploy, pvc, scaling...)

Hashicorp also offers a Helm module:

variable "if_clusterenabled" {
type = string
default = "true"
}


resource "helm_release" "redisexample" {
name = "my-redis-release"
repository = "https://charts.bitnami.com/bitnami"
chart = "redis"
version = "17.3.9"
namespace = "hcl"

values = [
"${file("values.yaml")}"
]

set {
name = "cluster.enabled"
value = var.if_clusterenabled
}

set {
name = "metrics.enabled"
value = "true"
}

}

asciicast

In this case, I can inject the variables:

  • from the values.yaml file
  • file, which are present in the terraform file

As said in the introduction of this article: the strength of terraform comes from its providers, so it is possible to get variables from a Vault server, a bitwarden, or even a KeePass.

k2tf - Migrating yaml to Terraform

To convince the lazy ones, here is a Github project to convert your YAML files to Terraform files. The generated files only need a provider.tf before they can be deployed.

asciicast

Conclusion

The HCL brings many advantages to the administration of a Kubernetes cluster. By integrating Terraform modules, we avoid a static configuration without weighing down the cluster. (for example, the use of initPods that can be used to retrieve content stored elsewhere while Terraform can do it during deployment). I look forward to seeing how Terraform and Kubernetes work in the future, and what integrations will be possible.

Pulumi?

But to open up other possibilities and get rid of a DSL, there is a handy tool called Pulumi that allows you to do the same things as Terraform using a real programming language like Python, Go, Java etc...

Thanks for reading!

· 6 min read
TheBidouilleur

Since the DevOps movement started (or rather Platform engineering), the topic of high availability has been brought to the forefront. And one of the most versatile solutions to achieve high availability is to create application clusters. (and so: containers)

So I've been running a Swarm cluster for a few years and I recently switched to Kubernetes (k3s to be precise). And by having clusters holding several hundreds of containers, we forget about maintenance and update.

And in this article, we will talk about updates.

Out-of-cluster container upgrade solutions

WatchTower

I think the best known solution is Watchtower

Watchtower is easy to use and is based (like many others) on labels. A label allows to define some parameters and to activate (or deactivate) the monitoring of updates.

Updating is not always good...

Be careful not to automatically update sensitive programs! We can't check what an update and if they won't break something. It's up to you to choose which applications to monitor, and to trigger an update or not.

WatchTower will notify you in several ways:

  • email
  • slack
  • msteams
  • gotify
  • shoutrrr

And among these methods, you do not have only proprietary solutions, free to you to host a shoutrrr, a gotify or to use your smtp so that this information does not leave your IS! *(I am very critical of the use of msteams, slack, discord to receive notifications)

WatchTower will scan for updates on a regular basis (configurable).

container-updater (from @PAPAMICA)

The most provided/complex solution is not always the best. Papamica has set up a bash script to meet his specific needs (which many other people must have): an update system notifying him through Discord and Zabbix.

This one is also based on labels and also takes care of the case where you want to update by docker-compose. (instead of doing a docker pull, docker restart like Watchtower)

labels:
- "autoupdate=true"
- "autoupdate.docker-compose=/link/to/docker-compose.yml"

Even if I don't use it, I had a time when I was using Zabbix and I needed to be notified on my Zabbix. (which notified me by Mail/Gotify)

Papamica states that he plans to add private registry support (for now only github registry or dockerhub) as well as other notification methods.

Solutions for Swarm

Swarm is probably the container orchestrator I enjoyed the most: it's **simple**! You learn fast, you discover fast and you get quick results. But I've already written about Swarm in another article...

Sheperd

What I like in Papamica's program (and that goes with Sheperd) is that we keep bash as the central language. A language that we all know in the main thanks to Linux, and that we can read and modify if we take the time.

Shepherd's code is only ~200 lines and works fine like that.

version: "3"
services:
...
shepherd:
build: .
image: mazzolino/shepherd
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
placement:
constraints:
- node.role == manager

This one will accept several private registers, which gives a nice advantage compared to the other solutions presented. Example:

    deploy:
labels:
- shepherd.enable=true
- shepherd.auth.config=blog

Shepherd does not include a (default) notification system. That's why its creator decided to offer a Apprise sidecar as an alternative. Which can redirect to many things like Telegram, SMS, Gotify, Mail, Slack, msteams etc....

I think this is the simplest and most versatile solution. I hope it will be found in other contexts. I hope it will be found in other contexts (but I don't go into too much detail on the subject, I'd like to write an article about it).

I used Shepherd for a long time and I had no problems.

Solutions for Kubernetes

For Kubernetes, we start to lose in simplicity. Especially since with the imagePullPolicy: Always option, you just have to restart a pod to get the last image with the same tag. For a long time, I used ArgoCD to update my configurations and re-deploy my images at each update on Git.

But ArgoCD is only used to update the configuration and not the image. The methodology is incorrect and it is necessary to find a suitable tool for that.

Keel.sh

Keel is a tool that meets the same need: Update pod images. But it incorporates several features not found elsewhere.

Keel

If you want to keep the same operation as the alternatives (i.e. regularly check for updates), it is possible:

metadata:
annotations:
keel.sh/policy: force
keel.sh/trigger: poll
keel.sh/pollSchedule: "@every 3m"

But where Keel excels is that it offers triggers and approvals.

A trigger is an event that will trigger the update of Keel. We can imagine a webhook coming from Github, Dockerhub, Gitea which will trigger the update of the server. *(So we avoid a regular crontab and we save resources, traffic and time) As the use of webhook has become widespread in CICD systems, it can be coupled to many use cases.

The approvals are the little gem that was missing from the other tools. Indeed, I specified that updating images is dangerous and you should not target sensitive applications in automatic updates. And it's just in response to that that Keel developed the approvals.

Approval system of keel

The idea is to give permission to Keel to update the pod. We can choose the moment and check manually.

I think it's a pity that we have Slack or MSTeams imposed for the approvals, it's then a feature that I won't use.

A UI

So for now, I use Keel without its web interface, it may bring new features, but I would like to avoid an umpteenth interface to manage.

Conclusion

Updating a container is not that easy when you are looking for automation and security. If today, I find that Keel corresponds to my needs, I have the impression that the tools are similar without offering real innovations. (I'm thinking of tackling the canary idea one day) I hope to discover new solutions soon, hoping that they will better fit my needs.

· 4 min read
TheBidouilleur

I'm in the middle of learning Kubernetes and solutions to manage a cluster, I'm practicing on a test cluster that has small containers on it like the one running thebidouilleur.xyz.

Longhorn is a must-have in the Kubernetes universe (and in particular k3s), I couldn't continue learning without dwelling on Longhorn. But first things first.

What is Longhorn?

Longhorn is presented in this simple sentence:

Longhorn is a lightweight, reliable and easy-to-use distributed block storage system for Kubernetes.

But we can go a little further than this simple sentence... Longhorn is a centralized storage system between cluster nodes. This means that instead of using an external storage like an NFS (or other, here is the list of possibilities we will be able to keep the data internally by using the disks of our machines present in the cluster.

And if you ask yourself the same question as me before knowing : Longhorn will make the equivalent of a RAID 0 by replicating the data on several nodes to avoid that the loss of a machine leads to the loss of data.

Concrete values

For example, counting the disks of my nodes I have 4x32Gio and 1x16Gio, that is 144Gio ( or 132Go because Rancher uses this value ). Of these 132GB, I currently occupy 36, I can use 56 on Longhorn, and I have 40 reserved for replicas. (by default, Rancher generates 3 replicas)

Dashboard longhorn

Comment déployer Longhorn ?

How to deploy Longhorn ?

link to official documentation

You can deploy Longhorn using Helm, the Rancher catalog or just through Kubectl

kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.3.0/deploy/longhorn.yaml

Version !

Be careful, this command will only deploy version 1.3.0 of longhorn, remember to get the last link in the documentation (or edit the link I put)

As a security measure, you should always check the contents in the applied yaml. Remember to take a look!

You'll have to wait until the pods deploy to start using Longhorn. To check the real time status, the documentation suggests the following command:

kubectl get pods \
--namespace longhorn-system \
--watch

But you can use k9s as well.

Once OK, we can deploy our first pod linked to longhorn.

Putting Longhorn into practice

Here is the manifest that we will deploy to use a volume in longhorn:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: longhorn-nginx-thebidouilleur-demo
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: longhorn-thebidouilleur-demo
namespace: default
spec:
containers:
- name: block-volume-test
image: nginx:stable-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: volume-longhorn
mountPath: "/usr/share/nginx/html"
ports:
- containerPort: 80
volumes:
- name: volume-longhorn
persistentVolumeClaim:
claimName: longhorn-nginx-thebidouilleur-demo

Volume OK

We ask for 1Gio to be allocated to this volume (it will influence the storage allocated for replicas) and we deploy a classic nginx. Once deployed, we will open a tunnel to this pod:

kubectl port-forward longhorn-thebidouilleur-demo 8080:80
tip

Knowing that the tunnel must open on your local machine (and not on one of the cluster nodes). I invite you to consult this page put kubectl on your machine.

and if we query the nginx, we obviously get a 403 error because the longhorn folder is empty. So we will create our index.html file directly from the pod.

kubectl exec longhorn-thebidouilleur-demo -i -t -- /bin/sh
echo "Hello World" > /usr/share/nginx/html/index.html

And by re-interrogating our pod: we find our Hello World.

[thebidouilleur@bertha ~]$ curl localhost:8080
Hello World

Now... it's very nice but do we keep our page in case of deletion of the pod?

kubectl delete pod longhorn-thebidouilleur-demo

We can see that on the longhorn dashboard: the volume has been switched to deattach. *(which means that the data are still present but not used on a pod)

We will re-apply the same manifest to recreate our pod and redo the same tunnel to access the nginx

[thebidouilleur@bertha ~]$ curl localhost:8080
Hello World

We have our "Hello World" page back!

Conclusion

Longhorn is an extremely easy to use tool that allows you to avoid creating an external solution to the cluster that would be less practical to manage. I didn't go very far in its features either and I let you make your own opinion for longhorn in production (and for that, go see the article of the site easyadmin.tech) Longhorn is welcome in my test Homelab and will be in the center of it !

· 4 min read
TheBidouilleur

Introduction

Mon premier cluster Kubernetes est actuellement en ligne. C'est encore un banc de test mais je l'ai pris prématurement en prod pour me forcer à l'administrer de manière sérieuse. Aujourd'hui (et en espérant que ça ait déjà évolué lorsque vous lirez l'article), mes pods utilisent un backend storage en NFS (accessible sur mon NAS).

Je veux que Kubernetes devienne mon manager de conteneur principal, je pense donc essentiel de découvrir les particularités générales de Kubernetes avant de commencer à y déployer des applications un peu plus complexes. Le stockage S3 est souvent référencé comme pratique et utile avec Kubernetes.

remarque

J'ai déjà utilisé Minio dans un autre contexte. Mais je ne compte pas utiliser Minio pour débuter S3, je veux une solution déjà prête et générique (apprendre la normalité avant de se spécialiser). Inutile de préciser qu'à l'avenir : Minio sera ma solution principale en Object Storage.

Je me suis donc orienté vers AWS Contabo qui propose une solution bien moins chère que notre amis américain. je paye 2,39€ mensuels pour 250Go à la place des 5,75€ demandés par Amazon.

Qu'est ce que le S3 ?

Pas besoin de faire une définition bancale, voici directement l'explication d'Amazon :

Amazon Simple Storage Service (Amazon S3) est un service de stockage d'objets qui offre une capacité de mise à l'échelle, une disponibilité des données, une sécurité et des performances de pointe. Les clients de toutes les tailles et de tous les secteurs peuvent stocker et protéger n'importe quelle quantité de données pour la quasi-totalité des cas d'utilisation, par exemple les lacs de données ainsi que les applications natives cloud et mobiles. Grâce à des classes de stockage économiques et à des fonctions de gestion faciles à utiliser, vous pouvez optimiser les coûts, organiser les données et configurer des contrôles d'accès affinés pour répondre à des exigences opérationnelles, organisationnelles et de conformité spécifiques.

Traduction : C'est une méthode performante et rapide de transférer des masses de données.

Et comment utiliser un stockage S3 ?

Il convient avant tout de rappeler une notion importante dans l'utilisation d'un Cloud : Un cloud n'est que l'ordinateur de quelqu'un d'autre

Si vous ne stockez pas chez vous : considerez que vos données peuvent être visionnées sans votre concentement. (Gouv, NSA, Mamie, Hacker etc...) Alors il convient de chiffrer vos données. Nous parlerons de Minio dans sur une autre page, une solution libre et open-source à héberger à la maison.

On peut dialoguer avec un serveur S3 via de nombreux outils :

Pour chiffrer mes données, je peux très bien passer par un simple script Bash chiffrant via GPG, puis envoyant les objets vers mon s3. Mais je n'apprécie pas cette solution bancale, et autant utiliser une solution all-in-one comme rclone ou restic. Et c'est effectivement avec restic que l'on va chiffrer et push les données.

Chiffrer puis envoyer ses objets

Comme dit précédemment : restic va être notre outil principal. Celui-ci fonctionne avec un système de "dépot"

Création du dépot restic

Restic permet de créer un dépot (qui peut être distant ou local), ce dépot chiffré sera le lieu où nous enverrons nos objets. Pour une première utilisation, on doit initialiser le dépot avec un restic init qui va créer la structure de fichier, et décider de la clé de chiffrement. Une fois le dépot créer, nous pourrons envoyer nos snapshots.

Restic autorise l'utilisation de variables d'environnement. On peut les définir avant d'utiliser restic.

export AWS_ACCESS_KEY_ID=ab5u8coxxpvjxwq4zu74jifmvfvxfu2y
export AWS_SECRET_ACCESS_KEY=3hs9sopqqto9sf8hhet8i92di987qcs6
bucketName="thebidouilleur" # variable séparée pour pouvoir la réutiliser ailleurs
export RESTIC_PASSWORD=Smudge9476 # Mot de passe de chiffrement
export RESTIC_REPOSITORY="s3:https://eu2.contabostorage.com/${bucketName}"

Ce ne sont pas mes vrais tokens, ne tentez pas d'utiliser les mêmes variables.

On peut enfin laisser restic créer notre dépot :

restic init

Si aucune erreur n'apparait ... félicitation ! On peut faire un restic backup pour créer notre première snapshot !

L'usage d'un S3 me permettra également de sauvegarder mes conteneurs utilisant des volumes sur Longhorn. Je pourrai ainsi sauvegarder mes données et les restaurer sur un autre cluster.